第十三次考试

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

我还是太弱了。。

posted @ 2018-08-15 00:22  lajioj  阅读(277)  评论(1编辑  收藏  举报