noip2016 假·解題報告

玩具謎題

題目描述

小南有一套可愛的玩具小人, 它們各有不一樣的職業。html

有一天, 這些玩具小人把小南的眼鏡藏了起來。 小南發現玩具小人們圍成了一個圈,它們有的面朝圈內,有的面朝圈外。以下圖:ios

這時singer告訴小南一個謎題: 「眼鏡藏在我左數第3個玩具小人的右數第1個玩具小人的左數第2個玩具小人那裏。 」c++

小南發現, 這個謎題中玩具小人的朝向很是關鍵, 由於朝內和朝外的玩具小人的左右方向是相反的: 面朝圈內的玩具小人, 它的左邊是順時針方向, 右邊是逆時針方向; 而面向圈外的玩具小人, 它的左邊是逆時針方向, 右邊是順時針方向。算法

小南一邊艱難地辨認着玩具小人, 一邊數着:數組

singer朝內, 左數第3個是archer。測試

archer朝外,右數第1個是thinker。spa

thinker朝外, 左數第2個是writer。htm

因此眼鏡藏在writer這裏!blog

雖然成功找回了眼鏡, 但小南並無放心。 若是下次有更多的玩具小人藏他的眼鏡, 或是謎題的長度更長, 他可能就沒法找到眼鏡了 。 因此小南但願你寫程序幫他解決相似的謎題。 這樣的謎題具體能夠描述爲:排序

有 n個玩具小人圍成一圈, 已知它們的職業和朝向。如今第1個玩具小人告訴小南一個包含 m條指令的謎題, 其中第 z條指令形如「左數/右數第 s,個玩具小人」。 你須要輸出依次數完這些指令後,到達的玩具小人的職業。

輸入輸出格式

輸入格式:

 

輸入的第一行包含兩個正整數 n,m, 表示玩具小人的個數和指令的條數。

接下來 n行, 每行包含一個整數和一個字符串, 以逆時針爲順序給出每一個玩具小人的朝向和職業。其中0表示朝向圈內, 1表示朝向圈外。保證不會出現其餘的數。字符串長度不超過10且僅由小寫字母構成, 字符串不爲空, 而且字符串兩兩不一樣。 整數和字符串之問用一個空格隔開。

接下來 m行,其中第 z行包含兩個整數 a,,s,,表示第 z條指令。若 a,= 0,表示向左數 s,我的;若a,= 1 ,表示向右數 s,我的。保證a,不會出現其餘的數, 1≤ s,<n 。

 

輸出格式:

 

輸出一個字符串, 表示從第一個讀入的小人開始, 依次數完 m條指令後到達的小人的職業。

 

輸入輸出樣例

輸入樣例#1:
7 3
0 singer
0 reader
0 mengbier 
1 thinker
1 archer
0 writer
1 mogician 
0 3
1 1
0 2
輸出樣例#1:
writer
輸入樣例#2:
10 10
1 C
0 r
     0 P
1 d
1 e
1 m
1 t
1 y
1 u
0 V
1 7
1 1
1 4
0 5
0 3
0 1
1 6
1 2
0 8
0 4
輸出樣例#2:
y

說明

【樣例1說明】

這組數據就是【題目描述】 中提到的例子。

【子任務】

子任務會給出部分測試數據的特色。 若是你在解決題目中遇到了困難, 能夠嘗試只解決一部分測試數據。

每一個測試點的數據規模及特色以下表:

其中一些簡寫的列意義以下:

• 全朝內: 若爲「√」, 表示該測試點保證全部的玩具小人都朝向圈內;

全左數:若爲「√」,表示該測試點保證全部的指令都向左數,即對任意的

1≤z≤m, ai=0;

s,= 1:若爲「√」,表示該測試點保證全部的指令都只數1個,即對任意的

1≤z≤m, si=1;

職業長度爲1 :若爲「√」,表示該測試點保證全部玩具小人的職業必定是一個

長度爲1的字符串。

 

很簡單的模擬。

有點小問題就是,存小人的時候要從0~n-1存,若是從1~n存就要在中間每一步都判斷%n後是否是0,若是是就+1。若是在最後判斷就會wa我也不知道爲啥。。。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string>
using namespace std;

struct qwq{
    int dir;
    char s[15];
}p[100005];
int n,m;
int a,s;
int now;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
        scanf("%d%s",&p[i].dir,p[i].s);
    }
    now=0;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&a,&s);
        if(p[now].dir==0&&a==1||p[now].dir==1&&a==0){
            now=(now+s)%n;
        }
        else{
            now=(now+n-s)%n;
        }
    }
    printf("%s\n",p[now].s);
    return 0;
}

  

 

 

 

蚯蚓

題目描述

本題中,咱們將用符號⌊c⌋\lfloor c \rfloorc⌋表示對c向下取整,例如:⌊3.0⌋=⌊3.1⌋=⌊3.9⌋=3\lfloor 3.0 \rfloor= \lfloor 3.1 \rfloor=\lfloor 3.9 \rfloor=33.0=3.1=3.9=3。

蛐蛐國最近蚯蚓成災了!隔壁跳蚤國的跳蚤也拿蚯蚓們沒辦法,蛐蛐國王只好去請神刀手來幫他們消滅蚯蚓。

蛐蛐國裏如今共有n只蚯蚓(n爲正整數)。每隻蚯蚓擁有長度,咱們設第i只蚯蚓的長度爲ai(i=1,2,...,n)a_i(i=1,2,...,n)ai(i=1,2,...,n),並保證全部的長度都是非負整數(即:可能存在長度爲0的蚯蚓)。

每一秒,神刀手會在全部的蚯蚓中,準確地找到最長的那一隻(若有多個則任選一個)將其切成兩半。神刀手切開蚯蚓的位置由常數p(是知足0<p<1的有理數)決定,設這隻蚯蚓長度爲x,神刀手會將其切成兩隻長度分別爲⌊px⌋\lfloor px \rfloorpx⌋和x−⌊px⌋x-\lfloor px \rfloorxpx⌋的蚯蚓。特殊地,若是這兩個數的其中一個等於0,則這個長度爲0的蚯蚓也會被保留。此外,除了剛剛產生的兩隻新蚯蚓,其他蚯蚓的長度都會增長q(是一個非負整常數)。

蛐蛐國王知道這樣不是長久之計,由於蚯蚓不只會愈來愈多,還會愈來愈長。蛐蛐國王決定求助於一位有着洪荒之力的神祕人物,可是救兵還須要m秒才能到來......

(m爲非負整數)

蛐蛐國王但願知道這m秒內的戰況。具體來講,他但願知道:

•m秒內,每一秒被切斷的蚯蚓被切斷前的長度(有m個數)

•m秒後,全部蚯蚓的長度(有n+m個數)。

蛐蛐國王固然知道怎麼作啦!可是他想考考你......

輸入輸出格式

輸入格式:

 

第一行包含六個整數n,m,q,u,v,t,其中:n,m,q的意義見【問題描述】;u,v,t均爲正整數;你須要本身計算p=u/v(保證0<u<v)t是輸出參數,其含義將會在【輸出格式】中解釋。

第二行包含n個非負整數,爲ai,a2,...,ana_i,a_2,...,a_nai,a2,...,an,即初始時n只蚯蚓的長度。

同一行中相鄰的兩個數之間,剛好用一個空格隔開。

保證1≤n≤1051 \le n \le 10^51n105,0<m≤7∗1060<m \le 7*10^60<m7106,0≤u<v≤1090 \le u<v \le 10^90u<v109,0≤q≤2000 \le q \le 2000q200,1≤t≤711 \le t \le 711t71,0<ai≤1080<ai \le 10^80<ai108。

 

輸出格式:

 

第一行輸出⌊m/t⌋\lfloor m/t \rfloorm/t⌋個整數,按時間順序,依次輸出第t秒,第2t秒,第3t秒……被切斷蚯蚓(在被切斷前)的長度。

第二行輸出⌊(n+m)/t⌋\lfloor (n+m)/t \rfloor(n+m)/t⌋個整數,輸出m秒後蚯蚓的長度;須要按從大到小的順序,依次輸出排名第t,第2t,第3t……的長度。

同一行中相鄰的兩個數之間,剛好用一個空格隔開。即便某一行沒有任何數須要 輸出,你也應輸出一個空行。

請閱讀樣例來更好地理解這個格式。

 

輸入輸出樣例

輸入樣例#1:
3 7 1 1 3 1
3 3 2
輸出樣例#1:
3 4 4 4 5 5 6
6 6 6 5 5 4 4 3 2 2
輸入樣例#2:
3 7 1 1 3 2
3 3 2
輸出樣例#2:
4 4 5
6 5 4 3 2
輸入樣例#3:
3 7 1 1 3 9
3 3 2
輸出樣例#3:
//空行
2

說明

【樣例解釋1】

在神刀手到來前:3只蚯蚓的長度爲3,3,2。

1秒後:一隻長度爲3的蚯蚓被切成了兩隻長度分別爲1和2的蚯蚓,其他蚯蚓的長度增長了1。最終4只蚯蚓的長度分別爲(1,2),4,3。括號表示這個位置剛剛有一隻蚯蚓被切斷

2秒後:一隻長度爲4的蚯蚓被切成了1和3。5只蚯蚓的長度分別爲:2,3,(1,3),4。

3秒後:一隻長度爲4的蚯蚓被切斷。6只蚯蚓的長度分別爲:3,4,2,4,(1,3)。

4秒後:一隻長度爲4的蚯蚓被切斷。7只蚯蚓的長度分別爲:4,(1,3),3,5,2,4。

5秒後:一隻長度爲5的蚯蚓被切斷。8只蚯蚓的長度分別爲:5,2,4,4,(1,4),3,5。

6秒後:一隻長度爲5的蚯蚓被切斷。9只蚯蚓的長度分別爲:(1,4),3,5,5,2,5,4,6。

7秒後:一隻長度爲6的蚯蚓被切斷。10只蚯蚓的長度分別爲:2,5,4,6,6,3,6,5,(2,4)。因此,7秒內被切斷的蚯蚓的長度依次爲3,4,4,4,5,5,6。7秒後,全部蚯蚓長度從大到小排序爲6,6,6,5,5,4,4,3,2,2

【樣例解釋2】

這個數據中只有t=2與上個數據不一樣。只需在每行都改成每兩個數輸出一個數便可。

雖然第一行最後有一個6沒有被輸出,可是第二行仍然要從新從第二個數再開始輸出。

【樣例解釋3】

這個數據中只有t=9與上個數據不一樣。

注意第一行沒有數要輸出,但也要輸出一個空行。

【數據範圍】

 

若是每次都把每一個蚯蚓拿出來+1,時間就會很是爆炸qwq...因此咱們在最後進行+1的操做,用一個三隊列維護當前最長的蚯蚓(把原來的隊列排序,每次切完把左部分放在一個隊列,右部分放在一個隊列,就能保證三個隊列都是單調遞減的,每次只要比較隊首元素就能夠獲得最長的蚯蚓)。每次切完以後-1再放入左/右隊列中(由於最後要把全部都看成沒有切過加上一段長度,多-的會被抵消).

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<string>
#include<cmath>
#include<queue>
using namespace std;

int n,m,q,u,v,t;
int a[100005];
int b[10000005];
int c[10000005];
int read(){
    char c;
    while((c=getchar())<'0'||c>'9');
    int sum=c-'0';
    while((c=getchar())>='0'&&c<='9'){
        sum=sum*10+c-'0';
    }
    return sum;
}
int ha,hb,hc;
int ta,tb,tc;


double p;
int ac,ans1,ans2;
double qwq;
int ans[10100005];
int cmp(int a,int b){
    return a>b;
}

int mm=0;

void gm(){
    if(ha<ta&&a[ha+1]>=b[hb+1]&&a[ha+1]>=c[hc+1]){
        ha++;
        ac=a[ha]+mm;
        a[ha]=0;
    }
    else {
        if(hb<tb&&b[hb+1]>=a[ha+1]&&b[hb+1]>=c[hc+1]){
            hb++;
            ac=b[hb]+mm;
            b[hb]=0;
        }
        else {
            hc++;
            ac=c[hc]+mm;
            c[hc]=0;
        }
    }
}


int main()
{
    n=read();
    m=read();
    q=read();
    u=read();
    v=read();
    t=read();
    ha=0;
    ta=n;
    hb=0;
    tb=0;
    hc=0;
    tc=0;
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    sort(a+1,a+1+n,cmp);
    a[n+1]=-999999999;
    for(int i=1;i<=m;i++){
        if(i%t==0){
            int maxx=a[ha+1];
            maxx=max(maxx,b[hb+1]);
            maxx=max(maxx,c[hc+1]);
            printf("%d ",maxx+(i-1)*q);
        }
        gm();
        ans1=(long long)ac*u/v;
        ans2=ac-ans1;
        ans1-=i*q;
        ans2-=i*q;
        b[++tb]=max(ans1,ans2);
        c[++tc]=min(ans1,ans2);
        mm+=q;
    }
    cout<<endl;
    for(int i=1;i<=n+m;i++){
        gm();
        ans[i]=ac;
        if(i%t==0)
        printf("%d ",ans[i]);
    } 
    
    return 0;    
}

  

 

憤怒的小鳥

題目描述

Kiana最近沉迷於一款神奇的遊戲沒法自拔。

簡單來講,這款遊戲是在一個平面上進行的。

有一架彈弓位於(0,0)處,每次Kiana能夠用它向第一象限發射一隻紅色的小鳥,小鳥們的飛行軌跡均爲形如y=ax2+bxy=ax^2+bxy=ax2+bx的曲線,其中a,b是Kiana指定的參數,且必須知足a<0。

當小鳥落回地面(即x軸)時,它就會瞬間消失。

在遊戲的某個關卡里,平面的第一象限中有n只綠色的小豬,其中第i只小豬所在的座標爲(xi,yi)。

若是某隻小鳥的飛行軌跡通過了(xi,yi),那麼第i只小豬就會被消滅掉,同時小鳥將會沿着原先的軌跡繼續飛行;

若是一隻小鳥的飛行軌跡沒有通過(xi,yi),那麼這隻小鳥飛行的全過程就不會對第i只小豬產生任何影響。

例如,若兩隻小豬分別位於(1,3)和(3,3),Kiana能夠選擇發射一隻飛行軌跡爲y=−x2+4xy=-x^2+4xy=x2+4x的小鳥,這樣兩隻小豬就會被這隻小鳥一塊兒消滅。

而這個遊戲的目的,就是經過發射小鳥消滅全部的小豬。

這款神奇遊戲的每一個關卡對Kiana來講都很難,因此Kiana還輸入了一些神祕的指令,使得本身能更輕鬆地完成這個遊戲。這些指令將在【輸入格式】中詳述。

假設這款遊戲一共有T個關卡,如今Kiana想知道,對於每個關卡,至少須要發射多少隻小鳥才能消滅全部的小豬。因爲她不會算,因此但願由你告訴她。

輸入輸出格式

輸入格式:

 

第一行包含一個正整數T,表示遊戲的關卡總數。

下面依次輸入這T個關卡的信息。每一個關卡第一行包含兩個非負整數n,m,分別表示該關卡中的小豬數量和Kiana輸入的神祕指令類型。接下來的n行中,第i行包含兩個正實數(xi,yi),表示第i只小豬座標爲(xi,yi)。數據保證同一個關卡中不存在兩隻座標徹底相同的小豬。

若是m=0,表示Kiana輸入了一個沒有任何做用的指令。

若是m=1,則這個關卡將會知足:至多用⌈n3+1⌉\left \lceil \frac{n}{3} + 1 \right \rceil3n+1⌉只小鳥便可消滅全部小豬。

若是m=2,則這個關卡將會知足:必定存在一種最優解,其中有一隻小鳥消滅了至少⌊n3⌋\left \lfloor \frac{n}{3} \right \rfloor3n⌋只小豬。

保證1<=n<=18,0<=m<=2,0<xi,yi<10,輸入中的實數均保留到小數點後兩位。

上文中,符號⌈x⌉\left \lceil x \right \rceilx⌉和⌊x⌋\left \lfloor x \right \rfloorx⌋分別表示對c向上取整和向下取整

 

輸出格式:

 

對每一個關卡依次輸出一行答案。

輸出的每一行包含一個正整數,表示相應的關卡中,消滅全部小豬最少須要的小鳥數量

 

輸入輸出樣例

輸入樣例#1:
2
2 0
1.00 3.00
3.00 3.00
5 2
1.00 5.00
2.00 8.00
3.00 9.00
4.00 8.00
5.00 5.00
輸出樣例#1:
1
1
輸入樣例#2:
3
2 0
1.41 2.00
1.73 3.00
3 0
1.11 1.41
2.34 1.79
2.98 1.49
5 0
2.72 2.72
2.72 3.14
3.14 2.72
3.14 3.14
5.00 5.00
輸出樣例#2:
2
2
3
輸入樣例#3:
1
10 0
7.16 6.28
2.02 0.38
8.33 7.78
7.68 2.09
7.46 7.86
5.77 7.44
8.24 6.72
4.42 5.11
5.42 7.79
8.15 4.99
輸出樣例#3:
6

說明

【樣例解釋1】

這組數據中一共有兩個關卡。

第一個關卡與【問題描述】中的情形相同,2只小豬分別位於(1.00,3.00)和 (3.00,3.00),只需發射一隻飛行軌跡爲y = -x^2 + 4x的小鳥便可消滅它們。

第二個關卡中有5只小豬,但通過觀察咱們能夠發現它們的座標都在拋物線 y = -x^2 + 6x上,故Kiana只須要發射一隻小鳥便可消滅全部小豬。

【數據範圍】

 

由於豬的數量少,因此咱們能夠用一個狀態說明每一個豬是死了仍是活着(0表明活着,1表明死了),用g[i][j]數組表示通過i和j兩隻豬的拋物線能使全部的豬變成什麼狀態。

 處理g數組的方法:枚舉i和j兩隻豬,從n->1枚舉第k只豬,枚舉前g[i][j]<<=1 若是能被經過,就個g[i][j]|=1;

最後用f數組進行狀壓dp,枚舉k爲當前狀態,先找到當前狀態下第一個沒有被殺死的豬記做i,再枚舉g[i][j](j>=i)來試圖殺死某些豬。最後直接輸出全部豬都被殺死的狀態的f就好啦~~

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<iostream>
#define INF 0x3f3f3f3f
#define MAXI (1 << 18)
#define eps 0.0000001
using namespace std;
int t,n,m;
double x[50];
double y[50];
int g[20][20];
int f[MAXI+2];
bool ki(int i,int j,int k)
{
    double a=(y[i]/x[i]-y[j]/x[j])/(x[i]-x[j]);
    if(a>=0) return 0;
    double b=(y[i]-a*x[i]*x[i])/x[i];
    if(abs(a*x[k]*x[k]+b*x[k]-y[k])<eps) return 1;
    return 0;    
}

int main()
{
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%lf%lf",&x[i],&y[i]);
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                g[i][j]=0;
                
            }
        }
        
        for(int k=1;k<(1 << n);k++){
            f[k]=INF;    
        }
        
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                
                for(int k=n;k;k--){
                    g[i][j] <<= 1;
                    if(ki(i,j,k)){
                        g[i][j] |= 1;
                    }
                }
                if(i==j) g[i][j]=1 << (i-1);
            }
        }
        
        for(int k=0;k<(1 << n);k++){
            int i;
            for(i=1;i<=n;i++)
                if(!(k & 1 << (i-1))) break;
            for(int j=i;i<=n;i++){
                f[k | g[i][j]]=min(f[k|g[i][j]],f[k]+1);    
            }
        }
        
        printf("%d\n",f[(1 << n)-1]);
    }
    return 0;
}

  

 

組合數問題

題目描述

組合數CnmC_n^mCnm表示的是從n個物品中選出m個物品的方案數。舉個例子,從(1,2,3) 三個物品中選擇兩個物品能夠有(1,2),(1,3),(2,3)這三種選擇方法。根據組合數的定 義,咱們能夠給出計算組合數的通常公式:

Cnm=n!m!(n−m)!C_n^m=\frac{n!}{m!(n - m)!}Cnm=m!(nm)!n!

其中n! = 1 × 2 × · · · × n

小蔥想知道若是給定n,m和k,對於全部的0 <= i <= n,0 <= j <= min(i,m)有多少對 (i,j)知足CijC_i^jCij是k的倍數。

輸入輸出格式

輸入格式:

 

第一行有兩個整數t,k,其中t表明該測試點總共有多少組測試數據,k的意義見 【問題描述】。

接下來t行每行兩個整數n,m,其中n,m的意義見【問題描述】。

 

輸出格式:

 

t行,每行一個整數表明答案。

 

輸入輸出樣例

輸入樣例#1:
1 2
3 3
輸出樣例#1:
1
輸入樣例#2:
2 5
4 5
6 7
輸出樣例#2:
0
7

說明

【樣例1說明】

在全部可能的狀況中,只有C21=2C_2^1 = 2C21=2是2的倍數。

【子任務】

 

 

組合數的遞推公式——c[i][j]=c[i-1][j]+c[i-1][j-1]l;

先用這個公式預處理c數組,再用s數組維護二維前綴和(0,0~2000,2000)

注意處理c數組的時候要把初始化成-1,而後遞推的時候特判,否則%k以後合法的地方也是0,合法的數量就會出錯。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
using namespace std;


int t,k;
int c[2025][2025];

int s[2025][2025];

void init() {
    memset(c,-1,sizeof(c));
    c[0][0]=1;
    for(int i=1; i<=2000; i++) 
        c[i][0]=1;
    for(int i=1; i<=2000; i++) 
        for(int j=1; j<=2000 && j<=i; j++)
            c[i][j]=(c[i-1][j-1]+(c[i-1][j]==-1?0:c[i-1][j]))%k;
    for(int i=1; i<=2000; i++){ 
        for(int j=1; j<=2000; j++){
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
            if(!c[i][j]){
                s[i][j]++;
            }
        }
    }
}

int main() {
    scanf("%d%d",&t,&k);
    init();
    while(t--) {
        int n,m;
        scanf("%d%d",&n,&m);
        printf("%d\n",s[n][m]);
    }

    return 0;
}

  

 

換教室

題目描述

對於剛上大學的牛牛來講,他面臨的第一個問題是如何根據實際狀況申請合適的課程。

在能夠選擇的課程中,有 2n2n2n 節課程安排在 nnn 個時間段上。在第 iii(1≤i≤n1 \leq i \leq n1in)個時間段上,兩節內容相同的課程同時在不一樣的地點進行,其中,牛牛預先被安排在教室 cic_ici 上課,而另外一節課程在教室 did_idi 進行。

在不提交任何申請的狀況下,學生們須要按時間段的順序依次完成全部的 nnn 節安排好的課程。若是學生想更換第 iii 節課程的教室,則須要提出申請。若申請經過,學生就能夠在第 iii 個時間段去教室 did_idi 上課,不然仍然在教室 cic_ici 上課。

因爲更換教室的需求太多,申請不必定能得到經過。經過計算,牛牛發現申請更換第 iii 節課程的教室時,申請被經過的機率是一個已知的實數 kik_iki,而且對於不一樣課程的申請,被經過的機率是互相獨立的。

學校規定,全部的申請只能在學期開始前一次性提交,而且每一個人只能選擇至多 mmm 節課程進行申請。這意味着牛牛必須一次性決定是否申請更換每節課的教室,而不能根據某些課程的申請結果來決定其餘課程是否申請;牛牛能夠申請本身最但願更換教室的 mmm 門課程,也能夠不用完這 mmm 個申請的機會,甚至能夠一門課程都不申請。

由於不一樣的課程可能會被安排在不一樣的教室進行,因此牛牛須要利用課間時間從一間教室趕到另外一間教室。

牛牛所在的大學有 vvv 個教室,有 eee 條道路。每條道路鏈接兩間教室,而且是能夠雙向通行的。因爲道路的長度和擁堵程度不一樣,經過不一樣的道路耗費的體力可能會有所不一樣。 當第 iii(1≤i≤n−11 \leq i \leq n-11in1)節課結束後,牛牛就會從這節課的教室出發,選擇一條耗費體力最少的路徑前往下一節課的教室。

如今牛牛想知道,申請哪幾門課程可使他因在教室間移動耗費的體力值的總和的指望值最小,請你幫他求出這個最小值。

輸入輸出格式

輸入格式:

 

第一行四個整數 n,m,v,en,m,v,en,m,v,e。nnn 表示這個學期內的時間段的數量;mmm 表示牛牛最多能夠申請更換多少節課程的教室;vvv 表示牛牛學校裏教室的數量;eee表示牛牛的學校裏道路的數量。

第二行 nnn 個正整數,第 iii(1≤i≤n1 \leq i \leq n1in)個正整數表示 cic_ici,即第 iii 個時間段牛牛被安排上課的教室;保證 1≤ci≤v1 \le c_i \le v1civ。

第三行 nnn 個正整數,第 iii(1≤i≤n1 \leq i \leq n1in)個正整數表示 did_idi,即第 iii 個時間段另外一間上一樣課程的教室;保證 1≤di≤v1 \le d_i \le v1div。

第四行 nnn 個實數,第 iii(1≤i≤n1 \leq i \leq n1in)個實數表示 kik_iki,即牛牛申請在第 iii 個時間段更換教室得到經過的機率。保證 0≤ki≤10 \le k_i \le 10ki1。

接下來 eee 行,每行三個正整數 aj,bj,wja_j, b_j, w_jaj,bj,wj,表示有一條雙向道路鏈接教室 aj,bja_j, b_jaj,bj,經過這條道路須要耗費的體力值是 wjw_jwj;保證 1≤aj,bj≤v1 \le a_j, b_j \le v1aj,bjv, 1≤wj≤1001 \le w_j \le 1001wj100。

保證 1≤n≤20001 \leq n \leq 20001n2000,0≤m≤20000 \leq m \leq 20000m2000,1≤v≤3001 \leq v \leq 3001v300,0≤e≤900000 \leq e \leq 900000e90000。

保證經過學校裏的道路,從任何一間教室出發,都能到達其餘全部的教室。

保證輸入的實數最多包含 333 位小數。

 

輸出格式:

 

輸出一行,包含一個實數,四捨五入精確到小數點後剛好222位,表示答案。你的輸出必須和標準輸出徹底同樣纔算正確。

測試數據保證四捨五入後的答案和準確答案的差的絕對值不大於 4×10−34 \times 10^{-3}4×103。 (若是你不知道什麼是浮點偏差,這段話能夠理解爲:對於大多數的算法,你能夠正常地使用浮點數類型而不用對它進行特殊的處理)

 

輸入輸出樣例

輸入樣例#1:
3 2 3 3
2 1 2
1 2 1
0.8 0.2 0.5 
1 2 5
1 3 3
2 3 1
輸出樣例#1:
2.80

說明

【樣例1說明】

全部可行的申請方案和指望收益以下表:

【提示】

  1. 道路中可能會有多條雙向道路鏈接相同的兩間教室。 也有可能有道路兩端鏈接

的是同一間教室。

2.請注意區分n,m,v,e的意義, n不是教室的數量, m不是道路的數量。

特殊性質1:圖上任意兩點 aia_iai, bib_ibi, aia_iaibib_ibi間,存在一條耗費體力最少的路徑只包含一條道路。

特殊性質2:對於全部的 $1≤ i≤ n$, ki=1k_i= 1ki=1 。

 

 

若是不會機率dp的話能夠用floyd獲得28分。然而感受這個轉移方程想出來了其實就。。。挺簡單?

用f[i][j][0/1]表示已通過去了i的時間,j表示已經用過的換教室的次數,0/1表示第i個時間是否試圖更換教室。每次能夠用i-1的狀態更新i的狀態,對於這一次換了/沒換分別算出前一次換了/沒換的指望路程,分別用最小值更新當前狀態。

由於最終的答案m能夠用任意次,因此要遍歷j求最小值。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<string>
#include<cmath>

using namespace std;

int n,m,v,e;

int c[2005];
int d[2005];
double k[2005];

int f[305][305];

void init()
{
    
    for(int k=1;k<=v;k++){
        for(int i=1;i<=v;i++){
            for(int j=1;j<=v;j++){
                if(f[i][k]+f[k][j]<f[i][j])
                    f[i][j]=f[i][k]+f[k][j];
            }
        }
    }
}

double dp[2005][2005][2],ans=1e30;

int main()
{
    scanf("%d%d%d%d",&n,&m,&v,&e);
    for(int i=1;i<=n;i++) scanf("%d",&c[i]);
    for(int i=1;i<=n;i++) scanf("%d",&d[i]);
    for(int i=1;i<=n;i++) scanf("%lf",&k[i]);
    for(int i=1;i<=v;i++){
        for(int j=1;j<=v;j++){
            if(i==j) f[i][j]=0;
            else f[i][j]=999999999;
        }
    }
    for(int i=1;i<=e;i++){
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        f[a][b]=min(f[a][b],w);
        f[b][a]=min(f[b][a],w);
        
    }
    init();
    if(n==1){
        printf("0.00\n");
        return 0;
    }
    if(!m){
        int ans=0;
        for(int i=1;i<n;i++){
            ans+=f[c[i]][c[i+1]];
        }
        printf("%.2lf",(double)ans);
        return 0;
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            dp[i][j][0]=dp[i][j][1]=1e30;
        }
    }
    dp[1][0][0]=dp[1][1][1]=0;//第一次換了或者沒換,指望的路程都是0
    for(int i=2;i<=n;i++){
        for(int j=0;j<=m;j++){
            double q1=dp[i-1][j][0]+f[c[i-1]][c[i]];
            double q2=dp[i-1][j][1]+f[c[i-1]][c[i]]*(1-k[i-1])+f[d[i-1]][c[i]]*k[i-1];
            dp[i][j][0]=min(q1,q2);
            if(j>=1){//若是此次換教室 
                q1=dp[i-1][j-1][0]+f[c[i-1]][c[i]]*(1-k[i])+f[c[i-1]][d[i]]*k[i];
                q2=dp[i-1][j-1][1];
                q2+=f[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]);
                q2+=f[c[i-1]][d[i]]*(1-k[i-1])*k[i];
                q2+=f[d[i-1]][d[i]]*k[i-1]*k[i];
                q2+=f[d[i-1]][c[i]]*k[i-1]*(1-k[i]);
                dp[i][j][1]=min(q1,q2);
            }
        }
    }
    for(int i=0;i<=m;i++){
        ans=min(ans,dp[n][i][0]);
        ans=min(ans,dp[n][i][1]);
    }
    printf("%.2lf\n",ans);
    return 0;    
}
相關文章
相關標籤/搜索