zoukankan      html  css  js  c++  java
  • 第十三次考试

    水题争霸赛,然而我最菜(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;
    }
    
    

    我还是太弱了。。

  • 相关阅读:
    Windows Phone 8 开发环境搭建
    常用正则表达式大全分享
    ios 使用NSRegularExpression解析正则表达式
    大整数类BIGN的设计与实现 C++高精度模板
    CODEVS_1227 方格取数2 网络流 最小费用流 拆点
    CODEVS_1034 家园 网络流 最大流
    CODEVS_1033 蚯蚓的游戏问题 网络流 最小费用流 拆点
    HDU_4770 Lights Against Dudely 状压+剪枝
    CODEVS_2144 砝码称重 2 折半搜索+二分查找+哈希
    CODEVS_1074 食物链
  • 原文地址:https://www.cnblogs.com/lajioj/p/9479029.html
Copyright © 2011-2022 走看看