暑期集训模拟赛2

前言

今天算是有一些进步吧,(T4)(zsq)学长友情讲解

NO.1 Layout

Descrption

和人类一样,奶牛们在打饭的时候喜欢和朋友站得很近。
约翰的编号为 (1)(n)(n(2le nle 1000)) 只奶牛正打算排队打饭。现在请你来安排她们,让她们在数轴上排好队。奶牛的弹性很好,同一个坐标可以站无限只奶牛,排队的顺序必须和她们编号的顺序一致。有 (M) 对奶牛互相爱慕,她们之间的距离不能超过一定的值,有 (K) 对奶牛互相敌视,她们的距离不能小于一定的值。
那么,首尾奶牛的最大距离是多少呢?

Input

第一行输入 (n,M,K,(0<M,Kle 5000))
接下来 (M) 行每行三个整数(x,y,z),表示编号为 (x)(y) 的两头奶牛之间的距离最大不超过 (z)
再接下来 (K) 行每行三个整数 (a,b,c),表示编号为 (a)(b) 的两头奶牛之间的距离最少为(c)

Output

如果没有合理方案,输出 (−1),如果首尾两头牛的距离可以无限大,输出 (−2),否则输出一个整数表示首尾奶牛的最大距离。

Sample Input

4 2 1
1 3 10
2 4 20
2 3 3

Sample Output

27

Hint

四只牛分别在 (0,7,10,27)

分析

其实就是一道裸的差分约束题,我们只需要从(0)开始建一个超级源点,并且反着每两个相邻点直间建边,边权为0,然后先从(0)开始(spfa),判断是否有负环和无限大,如果没有再从一开始,输出到(n)的最短距离。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+10;
const int maxm = 5e3+10;
struct Node{
	int v,next,val;
}e[maxm<<2];
int n,m,k;
int head[maxn],vis[maxn],dis[maxn];
int tot,c[maxn];
void Add(int x,int y,int z){
	e[++tot].v = y;
	e[tot].next = head[x];
	e[tot].val = z;
	head[x] = tot;
}
queue<int>q;
int spfa(int s){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	memset(c,0,sizeof(vis));
	dis[s] = 0;
	vis[s] = 1;
	q.push(s);
	while(!q.empty()){
		int x = q.front();
		q.pop();
		vis[x] = 0;
		c[x]++;
		if(c[x]>n)return -1;//判负环
		for(int i=head[x];~i;i=e[i].next){
			int v = e[i].v;
			int w = e[i].val;
			if(dis[v] > dis[x] + w){
				dis[v] = dis[x] + w;
				if(!vis[v]){
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
	if(dis[n]==0x3f3f3f3f)return -2;
	return dis[n];
}
int main(){
	memset(head,-1,sizeof(head));
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;++i){//不等式建边
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		Add(x,y,z);
	}
	for(int i=1;i<=k;++i){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		Add(y,x,-z);
	}
	for(int i=1;i<n;++i){//反着建一个边
		Add(i+1,i,0);
	}
	for(int i=1;i<=n;++i){//超级源点
		Add(0,i,0);
	}
	int jl = spfa(0);
	if(jl<=-1){//判断负环和无穷大
		printf("%d
",jl);
		return 0;
	}
	else printf("%d
",spfa(1));
	return 0;
}

NO.2 游戏

Descrption

(Mirko)(Slavko) 爱玩弹球戏。在一个令人激动的星期五,(Mirko)(Slavko) 玩了一把弹球游戏。(Mirko) 构建一个有向图,所有顶点最多有 (1) 条出边。弹球从 (1) 个顶点出发可以沿着一条边移动到它的邻接点,只要它存在,而且它会继续移动到后者的邻接点去,直到最后到达一个找不到出边的顶点才停下来。如果不存在这样的点,弹球可能无限运动下去。
为了确信 (Slavko)理解游戏的规则,(Mirko) 将发起一系列询问,询问的类型如下:
(1 X) :除非弹球陷入循环,弹球从 (X) 出发,最终将在哪个点停下来。
(2 X):删除 (X) 的出边(保证该边总是存在)
注意:询问是按顺序执行的。

Input

第一行包含一个正整数 (N(1le Nle 300000)),表示图的定点数。
第二行包含由空格隔开 (N) 个正整数,第 (i) 个数表示从 (i) 顶点可以通过出边到达的定点编号。(0) 表示该点没有出边。
接下来的一行包含 (1) 个整数 (Q(1le Qle 300000)),表示询问的次数。格式如上所示。

Output

对于第 (1) 类询问,输出弹球停止时所在顶点编号,每行 (1) 个,按照查询的顺序输出。如果弹球无法停止,则输出 (CIKLUS).

Sample Input

3
2 3 1
7
1 1
1 2
2 1
1 2
1 1
2 2
1 2

Sample Output

CIKLUS
CIKLUS
1
1
2

分析

因为涉及到好多删边,查询能到哪里的操作,所以我们用并查集来实现,把去点作为当前点的父亲。首先记录每一个操作,然后首先把边删掉,也就是先进行(2)操作。然后记录一下原来的父子关系,方便还原。然后我们开始倒序查询(1)操作,如果涉及到(2)操作,那么就还原,记录下来当前操作下的点的(fa),也就是答案,最后再进行正向的枚举,如果父亲不是(0),那么就输出父亲,否则输出无法停下。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10,maxm = 2e5+10;
int fa[maxn];
int ffa[maxn];//复制数组,便于还原
int n;
int a[maxn][2];
int Find(int x,int jl){
	if(jl>n)return fa[x] = 0;//如果为环,就让父亲为0。
	if(x == fa[x])return x;
	return fa[x] = Find(fa[x],jl+1);//每次记录经过的点加一。
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){//初始化
		fa[i] = i;
	}
	for(int i=1;i<=n;++i){
		int x;
		scanf("%d",&x);
		if(x!=0)fa[i] = ffa[i] = x;//有连接的点就让复制数组和父亲数组就记录。
	}
	int Q;
	scanf("%d",&Q);
	for(int i=1;i<=Q;++i){//进行删除操作,也就是父亲为自己
		scanf("%d%d",&a[i][0],&a[i][1]);
		if(a[i][0] == 2){
			fa[a[i][1]] = a[i][1];
		}
	}
	for(int i=Q;i>0;--i){//反着枚举
		if(a[i][0] == 1){
			a[i][1] = Find(a[i][1],0);
		}
		else{
			fa[a[i][1]] = ffa[a[i][1]];//还原
		}
	}
	for(int i=1;i<=Q;++i){
		if(a[i][0] == 1){
			if(a[i][1]){//不为0就输出
				printf("%d
",a[i][1]);
			}
			else{
				printf("CIKLUS
");
			}
		}
	}
	return 0;
}

NO.3 数字(数位dp)

Descrption

一个数字被称为好数字当他满足下列条件:
它有 (2 imes n) 个数位,(n) 是正整数(允许有前导 (0) )。
构成它的每个数字都在给定的数字集合 (S) 中。
它前 (n) 位之和与后 (n) 位之和相等或者它奇数位之和与偶数位之和相等。
例如对于 (n=2,S={1,2}),合法的好数字有 (1111,1122,1212,1221,2112,2121,2211,2222) 这样 (8) 种。
已知 (n),求合法的好数字的个数 (mod 999983)

Input

第一行一个数 (n)
接下来一个长度不超过 (10) 的字符串,表示给定的数字集合(不存在重复的数字)。

Output

一行,一个整数表示合法的好数字的个数 (mod 999983)

Sample Input

2
0987654321

Sample Output

1240

Hint

对于 (20\%) 的数据,(nle 7)
对于 (100\%) 的数据,(nle 1000,|S|le 10)

分析

(20)分的直接暴力就行,后边的需要用到(dp)
我们定义(f[i][j])表示前(i)个数字和为(j)的情况数,那么我们很容易推出来(f[i][j])的转移就是:

[f[i][j] = sum_{j=0}^{max(a_i)}sum_{k=1}^{len(s)}f[i-1][j-a[k]] ]

这样我们就处理出来了所有的(f[i][j]),然后开始统计答案。
首先考虑前(n)和后(n)个数和相等的情况,那么我们只需要考虑前(n)的情况就行了,总的情况数就是前(n)(f[n][i])的平方。
然后考虑奇数和偶数位和相等,其实也是一样的,我们只需要(n)个数,两个长度位(n)的序列交叉在一起就行,那么答案与上一种一样,所以最终的答案就是(2)倍。也就是:

[ans+=sum_{i=0}^{n imes max(a_n)}(f[n][i])^2 ]

而因为这两种情况里有交叉也就是多算了的情况,所以需要减去。
假设左边的奇数序列是(a),那么多算的情况就是右边的偶数序列是(a),那么减去这种就行了。前(n)位中奇数有(frac{n+1}{2})个,偶数位有(frac{n}{2})个,那么就可以得到需要减去的东西,即:

[ans-=sum_{i=0}^{frac{n+1}{2} imes max(a_n)}(f[frac{n+1}{2}][i])^2 imes sum_{j=0}^{frac{n}{2} imes max(a_n)}(f[frac{n}{2}][j])^2 ]

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e3+10;
const int maxm = 19;
const int mod = 999983;
char s[maxm];
int a[maxm];
int n;
ll f[maxn][maxn*9];
int main(){
    scanf("%d%s",&n,s+1);
    a[0] = strlen(s+1);
    for(int i=1;i<=a[0];++i){
        a[i] = s[i]-'0';//处理出每一位
    }
    f[0][0] = 1;
    for(int i=1;i<=n;++i){
        for(int j=0;j<=i*9;++j){
            for(int k=1;k<=a[0];++k){
                if(j>=a[k]){//计算总的情况
                    f[i][j] = (f[i][j] + f[i-1][j-a[k]])%mod;
                }
            }
        }
    }
    ll ans = 0;
    for(int i=0;i<=n*9;++i){
        ans = (ans + 2*f[n][i]*f[n][i])%mod;//算出总情况
    }
    int len1 = (n+1)/2;
    int len2 = n/2;
    ll ans1 = 0;
    ll ans2 = 0;
    for(int i=0;i<=len1*9;++i){//算出奇数位
        ans1 = (ans1 + f[len1][i]*f[len1][i]%mod)%mod;
    }
    for(int i=0;i<=len2*9;++i){//算出偶数位也就是上边式子的后半部分
        ans2 = (ans2 + f[len2][i]*f[len2][i]%mod)%mod;
    }
    ans = (ans - ans1*ans2%mod+mod)%mod;//减去
    printf("%lld
 ",ans%mod);
    return 0;
}

NO.4 水站

题目描述

已知有一个(n)层的水站:
(W_i)表示未操作之前第 (i) 层的已有水量;
(L_i)表示第 (i) 个水站能够维持或者储存的水的重量;
(P_i)表示在第 (i) 层进行减压放水操作所需的费用.
被压减放水层所储存的所有水都将流向下一层。如果第(i)层的水量比(L_i) 大,则这一层也会(自动)减压(不需要任何费用)。
现在想要使最后一层减压(第 (n)级),求最少的花费。这个任务现在交给了你。

输入格式

每个输入的第一行包含一个自然数(n(1le nle 150000))
接下来 (n) 行每行包含 (3) 个数 (W_i,L_i,P_i(0le W_i,L_i,P_ile 15000))

输出格式

第一行输出所需的最小费用
第二行若干个整数,从小到大输出必须减压的层的编号。

样例

样例输入

3
1000 1000 1
0 1000 2
2 10 100

样例输出

3
1 2

数据范围与提示

给第一层和第二层减压
(30\%:nle 5000)
(100\%:nle 150000)

分析

看到题目我们可以分析一下, 如果上边的一堆都减压了,但是有一个地方没有减压,这是对最后一层没有用的,所以减压的话肯定是一串连到最后一层,很容易能想出(O(n^2))的暴力。但是这样对于这个数据应该是过不去的(优良传统写正解),所以我们用到差分和前缀和。

我们设(c_i)为从(i)(n)一直放水的费用。我们考虑其中的每一个点能否不花钱自动防水,只有从(i)(k)的所有水都不能让他自动放水,那么其中应该要包含(k)的费用,而这个(i)肯定也是连续的一段,所以我们需要找到最小的那个(i),然后在这一段内的(c_i)都加上(p_k),这里用差分数组就可以。时间复杂度(O(n log n))

#include<bits/stdc++.h>
using namespace std;
const int maxn = 15e4+10;
int w[maxn],l[maxn],p[maxn];
int n;
int jl;
int c[maxn],sum[maxn];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%d%d%d",&w[i],&l[i],&p[i]);
        sum[i] = sum[i-1]+w[i];//记录前缀和,二分用
    }
    for(int i=1;i<=n;++i){
        int x = lower_bound(sum,sum+i+1,sum[i]-l[i])-sum;//二分找到上边所说的最小的i(这里是x)
        x++;
        c[x] += p[i];//差分数组,这个点的值加p[i]
        c[i+1] -= p[i];//后边这个点的值减去p[i]
    }
    int ans = 0x3f3f3f3f;
    for(int i=1;i<=n;++i){
        c[i] = c[i] + c[i-1];//差分数组记录的,所以想让这一段都加上,就需要每一次都加上一个的
        if(ans > c[i]){//记录最小
            ans = c[i];
            jl = i;
        }
    }
    cout<<ans<<endl;//输出最小花费
    int tot = 0;
    for(int i=jl;i<=n;++i){//找到哪几个需要花钱
        tot += w[i];
        if(tot<=l[i])printf("%d ",i);//输出
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Vocanda/p/13325142.html