第十三次考试

第十三次考试

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

我还是太弱了。。