1. 程式人生 > >第十三次考試

第十三次考試

b+ name out 數據 存在 不同 可選 時間 clu

水題爭霸賽,然而我最菜\(qwq\)

T1 導航

【題目描述】:
約翰在他的新車上裝了兩個導航系統\((GPS)\),但這兩個\(GPS\)選擇的導航線路常常不同,約翰很是惱火。
約翰所在的小鎮地圖由\(N\)個路口和\(M\)條單向道路構成,兩個路口間可能有多條道路相連。約翰的家在\(1\)號路口,他的農場在\(N\)號路口。約翰從家出發,可以經過一系列的道路,最終到達農場。
兩個\(GPS\)用的都是上述地圖,但是,它們計算時間的算法不同。比如,經過第\(i\)條道路,\(1\)\(GPS\)計算出的時間是\(P_i\)分鐘,而\(2\)\(GPS\)算出的時間是\(Q_i\)分鐘。
約翰想要駕車從家到農場,但是,如果一個\(GPS\)

認為約翰當前行走的這條路不在它算出的最短路徑中,該\(GPS\)就會大聲抱怨約翰走錯了路。更倒黴的是,有可能兩個\(GPS\)會同時抱怨約翰當前走的路不是它們推薦的。
請幫助約翰計算,從家到農場過程中,選擇怎樣的路徑才能使得\(GPS\)抱怨的次數最少,請算出這個最少的抱怨次數。如果一條路上兩個\(GPS\)都在抱怨,算兩次\((+2)\)抱怨。

【輸入格式】:
\(1\)行: 兩個空格間隔的整數:\(N\)\(M\)
接下來\(M\)行,每行描述一條道路。第\(i\)行描述第\(i\)條道路,由四個空格間隔的整數構成,\(A_i\),\(B_i\),\(P_i\),\(Q_i\),分別表示該條道路的起點、終點、\(1\)

\(GPS\)計算的耗時、\(2\)\(GPS\)計算的耗時。

【輸出格式】:
\(1\)行: \(1\)個整數, 表示所求答案。

【樣例輸入】:

5 7
3 4 7 1
1 3 2 20
1 4 17 18
4 5 25 3
1 2 10 1
3 5 4 14
2 4 6 5

【樣例輸出】:

1

【數據範圍】:
對於\(30\%\) 的數據,\(1<= N <=20\),\(1<= M <=20\)
對於\(100\%\)的數據,\(1<= N <=10000\) ,\(1 <= M <= 50000\) ,\(0<=Pi,Qi<=100000\)

思路:

因為每到一個新的節點後,原來所計算出來的最短路可能會被更新,所以應該比較自然地想到先反向建邊,求出\(n\)

到所有節點的最短路徑,所以先跑兩次\(spfa\),分別用\(GPS1\)\(GPS2\)作為邊權,得到兩個\(dis\)數組。
對於兩次\(spfa\),記錄兩個\(pre\)數組記錄路徑,那麽對於這兩條路徑,進行如下處理:

  • 如果存在\(u,v\),它們既在\(pre1\)中,又在\(pre2\)中,顯然經過這條路徑時,兩個\(GPS\)都不會抗議,就連一條權為\(0\)的邊
  • 如果存在\(u,v\),它們僅在\(pre1\)中,或僅又在\(pre2\),顯然經過這條路徑時,其中一個\(GPS\)會抗議,就連一條權為\(1\)的邊,
  • 對於其他路徑,顯然兩個\(GPS\)都會抗議,就連一條權為\(2\)的邊

然後再跑一邊\(spfa\),最短路的值即為答案。
這麽水我居然只有80

#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;

int n,m;

const int MAXM = 50005;
const int MAXN = 10005;

struct edge{
    int u,v,w1,w2,nxt,w;
}e[MAXM];int head[MAXN];int cnt=0;int dis1[MAXN],dis2[MAXN];int r[MAXN];int pre1[MAXN],pre2[MAXN];int tnt=0;
struct EDGE{
    int u,v,w,nxt;
}a[MAXM];int dis[MAXN];int last[MAXN];

inline void add(int u,int v,int w){
    a[++tnt].u = u;a[tnt].v = v;a[tnt].w = w;a[tnt].nxt = last[u];last[u] = tnt;
}

inline void add(int u,int v,int w1,int w2){
    e[++cnt].u = u;e[cnt].v = v;e[cnt].w1 = w1;e[cnt].w2 = w2;e[cnt].nxt = head[u];head[u] = cnt;
}

queue<int>q;

inline void spfa1(){
    q.push(n);memset(dis1,inf,sizeof dis1);
    dis1[n] = 0;
    while(!q.empty()){
        int u = q.front();q.pop();r[u] = 0;
        for(int i=head[u];i;i=e[i].nxt){
            int v = e[i].v;
            if(dis1[v] > e[i].w1 + dis1[u]){
                pre1[v] = i;
                dis1[v] = e[i].w1 + dis1[u];
                if(!r[v]){
                    r[v] = 1;
                    q.push(v);
                }
            }
        }
    }
}

inline void spfa2(){
    q.push(n);memset(dis2,inf,sizeof dis2);memset(r,0,sizeof r);
    dis2[n] = 0;
    while(!q.empty()){
        int u = q.front();q.pop();r[u] = 0;
        for(int i=head[u];i;i=e[i].nxt){
            int v = e[i].v;
            if(dis2[v] > e[i].w2 + dis2[u]){
                pre2[v] = i;
                dis2[v] = e[i].w2 + dis2[u];
                if(!r[v]){
                    r[v] = 1;
                    q.push(v);
                }
            }
        }
    }
}

inline void spfa(){
    q.push(1);memset(dis,inf,sizeof dis);memset(r,0,sizeof r);
    dis[1] = 0;
    while(!q.empty()){
        int u = q.front();q.pop();r[u] = 0;
        for(int i=last[u];i;i=a[i].nxt){
            int v = a[i].v;
            if(dis[v] > a[i].w + dis[u]){
                dis[v] = a[i].w + dis[u];
                if(!r[v]){
                    r[v] = 1;
                    q.push(v);
                }
            }
        }
    }
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i){
        int u,v,w1,w2;scanf("%d%d%d%d",&u,&v,&w1,&w2);
        add(v,u,w1,w2);
        add(u,v,2);
    }
    spfa1();
    spfa2();
    for(int i=1;i<=n;++i){
        if(pre1[i] == pre2[i]) a[pre1[i]].w=0;
        if(pre1[i] != pre2[i]){
            a[pre1[i]].w = a[pre2[i]].w = 1;
        }
    }
    spfa();
    printf("%d",dis[n]);
    return 0;
}

T2 比賽

【題目描述】:
有三個小夥伴組隊去參加 \(ACM\) 比賽,這場比賽共有\(n\)道題目,他們的比賽策略是這樣的:每個隊員都會對題目通看一遍,然後對每個題的難度進行估算,難度範圍為 \(1-9\)。當然,由於每個隊員的水平和特點, 他們對同一道題的估算不一定相同。
接下來他們會對所有題目進行分配。三個人分配的題目剛好是所有題目,且不會有交集,而且每個人分配的題目的編號必須是連續的,每人至少要 分一道題。請問,如何分配題目可以使得三個人拿到的題目的難度之和最小。每個人對自己 分配到的題目只按自己的估算值求和。

【輸入格式】:
第一行一個數 \(n\),表示題目的數量。
接下來有 \(3\) 行,每行表示一個學生,每行有 \(n\) 個數,表示該生對\(n\)道題的估算難度,難度介於 \([1,9]\)

【輸出格式】:
一個整數。表示最小的估算難度之和。

【樣例輸入1】:

3 
1 3 3 
1 1 1 
1 2 3

【樣例輸出1】:

4

【樣例輸出2】:

5
4 1 5 2 4 
3 5 5 1 1 
4 1 4 3 1

【樣例輸出2】:

11

【數據範圍】:
對於 \(20\%\) 的數據:\(3 <= N <= 1000\)
對於 \(100\%\) 的數據:\(3 <= N <= 200000\)

思路:

先放個正解的思路吧,我雖然\(AC\)了但是是歪門邪道。。
考點:前綴和
序列分成三段,三個人可以分別挑一段,總的方案數是排列數\(A_3^3\),於是有\(6\)種情況需要討論。
對每種情況分別考慮,預處理每個人的前綴和:\(SumX[]\)\(SumY[]\)\(SumZ[]\)
假設x,y,z三個人分別做\([1,j], [j+1,i], [i+1,n] 1<=j<i<n\)
\(Total=(SumX[j]-0)+(SumY[i]-SumY[j])+(SumZ[n]-SumZ[i])\)
\(=SumZ[n]+(SumX[j]-SumY[j])+(SumY[i]-SumZ[i])\)
\(SumZ[n]\)是一個定值,我們只需討論\((SumY[i]-SumZ[i])\)\((SumX[j]-SumY[j])\)的關系

從左到右枚舉\(i\),記錄i左邊出現過的最小的\((SumX[j]-SumY[j]) 1<=k<i\),更新答案
討論一遍的時間復雜度為\(O(n)\),總共討論\(6\)次,所以總的時間復雜度為\(O(6n)\)

總時間復雜度為$O(6n) $

\(std\)

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
inline void _read(long long &d)
{
    char t=getchar();bool f=false;
    while(t<'0'||t>'9'){if(t=='-')f=true;t=getchar();}
    for(d=0;t>='0'&&t<='9';t=getchar())d=d*10+t-'0';
    if(f==true)d=-d;
}
long long ans=9999999999;
long long n;
long long sum[3][200005];
long long f[6][200005];
long long derta[6][200005];
void prepare()
{
    long long a=1;
    derta[0][a]=sum[0][a]-sum[1][a];
    derta[1][a]=sum[1][a]-sum[0][a];
    derta[2][a]=sum[0][a]-sum[2][a];
    derta[3][a]=sum[2][a]-sum[0][a];
    derta[4][a]=sum[2][a]-sum[1][a];
    derta[5][a]=sum[1][a]-sum[2][a];
    for(a=2;a<=n;a++)derta[0][a]=min(derta[0][a-1],sum[0][a]-sum[1][a]);
    for(a=2;a<=n;a++)derta[1][a]=min(derta[1][a-1],sum[1][a]-sum[0][a]);
    for(a=2;a<=n;a++)derta[2][a]=min(derta[2][a-1],sum[0][a]-sum[2][a]);
    for(a=2;a<=n;a++)derta[3][a]=min(derta[3][a-1],sum[2][a]-sum[0][a]);
    for(a=2;a<=n;a++)derta[4][a]=min(derta[4][a-1],sum[2][a]-sum[1][a]);
    for(a=2;a<=n;a++)derta[5][a]=min(derta[5][a-1],sum[1][a]-sum[2][a]);
}
void dealit()
{
    long long a,b,c,d,e;
    for(a=2;a<=n-1;a++)
    {
        b=sum[1][a]+derta[0][a-1]+sum[2][n]-sum[2][a];
        ans=min(b,ans);
    }
    for(a=2;a<=n-1;a++)
    {
        b=sum[1][a]+derta[4][a-1]+sum[0][n]-sum[0][a];
        ans=min(b,ans);
    }
    for(a=2;a<=n-1;a++)
    {
        b=sum[0][a]+derta[3][a-1]+sum[1][n]-sum[1][a];
        ans=min(b,ans);
    }
    for(a=2;a<=n-1;a++)
    {
        b=sum[0][a]+derta[1][a-1]+sum[2][n]-sum[2][a];
        ans=min(b,ans);
    }
    for(a=2;a<=n-1;a++)
    {
        b=sum[2][a]+derta[5][a-1]+sum[0][n]-sum[0][a];
        ans=min(b,ans);
    }
    for(a=2;a<=n-1;a++)
    {
        b=sum[2][a]+derta[2][a-1]+sum[1][n]-sum[1][a];
        ans=min(b,ans);
    }
}
int main()
{
    long long a,b,c,d,e;
    freopen("data10.in","r",stdin);
//  freopen("test.out","w",stdout);
    _read(n);
    for(a=0;a<=2;a++)
    {
        for(b=1;b<=n;b++)
        {
        _read(c);
        sum[a][b]=sum[a][b-1]+c;
        }
    }
    prepare();
    dealit();
    cout<<ans;
    return 0;
}

然而我。。。。。。。。。。。
仔細分析一下題目,我開始以為是二分,但是打了半個小時後覺得沒法分,重新讀題,發現這個題面可以轉化一下:
給出一個\(3*n\)的矩陣,求把它從三行,分成連續的三個區間,所得的總和最小值
有沒有點矩陣取數的感覺啊?
技術分享圖片
唯一的不同,就是轉移方程。
\(f[i][j] = min(f[i][j-1] , f[i-1][j-1]) + a[i][j]\)
然後把\(6\)種情況枚舉一遍,輸出最小值。。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define inf 0x3f3f3f3f

using namespace std;

const int MAXN = 200005;

int dif[4][MAXN];int dp[4][MAXN];

int main(){
    int n;scanf("%d",&n);int ans = inf;
    for(int i=1;i<=3;++i){
        for(int j=1;j<=n;++j){
            scanf("%d",&dif[i][j]);
        }
    }
    //round 1
    dif[1][n] += inf;dif[1][n-1] += inf;
    dif[2][n] += inf;dif[1][1] -= inf;
    for(int i=1;i<=3;++i){
        for(int j=1;j<=n;++j){
            dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
        }
    }
    ans = min(ans , dp[3][n]+inf);
    dif[1][n] -= inf;dif[1][n-1] -= inf;
    dif[2][n] -= inf;dif[1][1] += inf;
    //round 2
    memset(dp,0,sizeof dp);
    swap(dif[2] , dif[3]);
    dif[1][n] += inf;dif[1][n-1] += inf;
    dif[2][n] += inf;dif[1][1] -= inf;
    for(int i=1;i<=3;++i){
        for(int j=1;j<=n;++j){
            dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
        }
    }
    ans = min(ans , dp[3][n]+inf);
    dif[1][n] -= inf;dif[1][n-1] -= inf;
    dif[2][n] -= inf;dif[1][1] += inf;
    //round 3
    memset(dp,0,sizeof dp);
    swap(dif[1] , dif[3]);
    
    dif[1][n] += inf;dif[1][n-1] += inf;
    dif[2][n] += inf;dif[1][1] -= inf;
    for(int i=1;i<=3;++i){
        for(int j=1;j<=n;++j){
            dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
        }
    }
    ans = min(ans , dp[3][n]+inf);
    dif[1][n] -= inf;dif[1][n-1] -= inf;
    dif[2][n] -= inf;dif[1][1] += inf;
    //round 4
    memset(dp,0,sizeof dp);
    swap(dif[2] , dif[3]);
    
    dif[1][n] += inf;dif[1][n-1] += inf;
    dif[2][n] += inf;dif[1][1] -= inf;
    for(int i=1;i<=3;++i){
        for(int j=1;j<=n;++j){
            dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
        }
    }
    ans = min(ans , dp[3][n]+inf);
    dif[1][n] -= inf;dif[1][n-1] -= inf;
    dif[2][n] -= inf;dif[1][1] += inf;
    //round 5
    memset(dp,0,sizeof dp);
    swap(dif[1] , dif[3]);
    
    dif[1][n] += inf;dif[1][n-1] += inf;
    dif[2][n] += inf;dif[1][1] -= inf;
    for(int i=1;i<=3;++i){
        for(int j=1;j<=n;++j){
            dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
        }
    }
    ans = min(ans , dp[3][n]+inf);
    dif[1][n] -= inf;dif[1][n-1] -= inf;
    dif[2][n] -= inf;dif[1][1] += inf;
    //round 6
    memset(dp,0,sizeof dp);
    swap(dif[2] , dif[3]);
    
    dif[1][n] += inf;dif[1][n-1] += inf;
    dif[2][n] += inf;dif[1][1] -= inf;
    for(int i=1;i<=3;++i){
        for(int j=1;j<=n;++j){
            dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
        }
    }
    ans = min(ans , dp[3][n]+inf);
    dif[1][n] -= inf;dif[1][n-1] -= inf;
    dif[2][n] -= inf;dif[1][1] += inf;
    
    printf("%d",ans);
    return 0;
}

T3 澆花

【題面描述】:
\(n\) 個非負整數排成一行,每個數值為$ A_i$,數的位置不可改變。需要讓所有的數都恰好等於 \(h\)。可進行的操作是:對任意長度的區間\([i,j]\)中的每個數都加$ 1\(,\)i$ 和 \(j\) 也任選,但要求每個數只能作為一次區間的起點,也只能作為一次區間的終點。也即是說: 對任意的兩個區 間\([L_1,R_1]\)\([L_2,R_2]\), 要求:\(L1≠L2\) 並且 \(R1 ≠ R2\).
請問有多少種不同的方式,使所有的數都等於 \(h\).
輸出答案模 \(1000000007 (10^9+7)\)後的余數。 兩種方式被認為不同,只要兩種方式所實施的操作的區間集合中,有一個區間不同即可。

【輸入格式】:
\(1\)行:\(2\) 個整數 \(n, h\)
接下來$n $行,每行 \(1\) 個整數,表示\(A_i\)

【輸出格式】:
\(1\)行:\(1\) 個整數,表示答案。

【樣例輸入1】:

3 2 
1 1 1

【樣例輸出1】:

4

【樣例輸入2】:

5 1
1 1 1 1 1

【樣例輸出2】:

1

【樣例輸入3】:

4 3 
3 2 1 1

【樣例輸出3】:

0

【數據範圍】:
\(30\%\)的數據, \(1≤n, h≤30\)
\(100\%\)的數據,\(1≤n, h≤2000 1≤Ai≤2000\)

思路:

題解用的區間\(dp\),但還有更優秀的解法。
題解:
考點 區間動態規劃
類似於括號\(dp\)的討論方式,討論\(i\)的左邊,選哪個數字作為區間的起點,更新\(i\)的值
\(dp[i][k]\)表示從左往右討論到第\(i\)個數字,\(i\)的左邊有\(k\)個數字還未被用過(被當做區間的左起點), 的方案數。
分兩種情況討論:

情況\(1\)\(i\)被別人更新(因為i前面的k個數,任選一個為區間起點,都可更新到\(i\)):
\(a[i]+k==h\) 則$dp[i][k]=dp[i+1][k-1]*k+dp[i+1][k] $
說明,條件\(a[i]+k==h\),因為\(i\)左邊有\(k\)個數字還沒用過,那麽以這\(k\)個數字作為區間左起點可以操作\(k\)次,每次都可以更新到\(i\),更新\(k\)次,恰好就能使\(a[i]\)變成\(h\)
現在對於\(i\)而言,有兩種選擇, 使用\(i\)或者不使用\(i\)
若用\(i\)作為區間右端點,因為i只能當一次區間終點,所以只能從前\(k\)個中選一個來與它配對,故有\(k\)種方案,\(k\)個數中\(i\)選了一個,對於\(i+1\)它左邊就只有\(k-1\)個未使用的數了,數量總數為\(k*dp[i+1][k-1]\)
註意,這裏\(i\)不能再作為區間的左端點了,這樣的話會導致\(i\)被多更新一次,高度變成\(h+1\)
若不用\(i\)作為區間端點,則方案數為\(dp[i+1][k]\)

情況\(2\)\(i\)作為區間起點去更新別人
\(a[i]+k+1=h\)\(dp[i][k]=dp[i+1][k]*(k+1)+dp[i+1][k+1]\)
說明,因為\(i\)前面有\(k\)個數未被當做左起點使用,全部操作都只能把\(a[i]\)更新到\(h-1\)這個高度,那麽\(i\)號點必須自己作為某區間的左起點更新一次,在更新這個區間的同時把自己的高度也更新\(1\),達到\(h\)
這樣,對於下一個數\(i+1\)而言,算上\(i\)號點,它左側有\(k+1\)個點可選做區間左端點,任選一個選後剩下\(k\)個點,狀態\(dp[i+1][k]\)
若不用\(i\)作為區間左端點,則方案數為\(dp[i+1][k+1]\)

時間復雜度\(O(n^2)\),實現時采用記憶化搜索比較方便。

#include<iostream>
#include<cstdio>

using namespace std;
#define MOD 1000000007

long long dp[2010][2010];
int a[2010];
inline void add(long long &a,long long b){
   a += b;
   a %= MOD;
}
int main()
{
    int n,h;

     cin >> n >> h;

     for (int i = 1; i <= n ;i ++)
         cin >> a[i];

     dp[1][0] = (a[1] == h || a[1] + 1 == h?1:0);
     dp[1][1] = (a[1] + 1 == h?1:0);

     for (int i = 2;i <= n + 1; i ++)
      for (int open = max(0,h - a[i]- 1); open <= min(h - a[i],i) ; open ++){
          if (a[i] + open == h){
             add(dp[i][open] , dp[i - 1][open]);
             if (open > 0)
                add(dp[i][open] , dp[i - 1][open - 1]);
          }
          if (open + a[i] + 1 == h){
               add(dp[i][open] , dp[i - 1][open + 1] * (open + 1));
               add(dp[i][open] , dp[i - 1][open]);
               add(dp[i][open] , dp[i - 1][open] * open);
          }
      }

     cout << dp[n][0] << endl;
     return 0;
}

什麽\(O(n^2)\),明明\(O(n)\)好不好
詳見:這個懶得寫了\(233\)

#include<cstdio>
using namespace std;
typedef long long ll;

int n,m;

const int MAXN = 2005;
const ll mod = 1e9+7;

int a[MAXN];int b[MAXN];

inline int ABS(int x){return x < 0 ? -x : x;}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        a[i] = m - a[i];
        if(i == 1 && a[i] > 1) {puts("0");return 0;}
        if(i == n && a[i] > 1) {puts("0");return 0;};
        if(a[i] < 0) {puts("0");return 0;}
    }
    for(int i=1;i<=n;++i){
        b[i] = a[i] - a[i-1];
        if(ABS(b[i]) > 1){puts("0");return 0;}
    }
    
    ll cnt = 0;ll ans = 1;
    for(int i=1;i<=n;++i){
        if(b[i] == 1) cnt++;
        if(b[i] == -1) ans = (ans * cnt) % mod,cnt--;
        if(b[i] == 0) ans = (ans * (cnt + 1)) % mod;
    }
    printf("%lld",ans);
    return 0;
}

我還是太弱了。。

第十三次考試