前言
今天算是有一些进步吧,(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]),然后开始统计答案。
首先考虑前(n)和后(n)个数和相等的情况,那么我们只需要考虑前(n)的情况就行了,总的情况数就是前(n)的(f[n][i])的平方。
然后考虑奇数和偶数位和相等,其实也是一样的,我们只需要(n)个数,两个长度位(n)的序列交叉在一起就行,那么答案与上一种一样,所以最终的答案就是(2)倍。也就是:
而因为这两种情况里有交叉也就是多算了的情况,所以需要减去。
假设左边的奇数序列是(a),那么多算的情况就是右边的偶数序列是(a),那么减去这种就行了。前(n)位中奇数有(frac{n+1}{2})个,偶数位有(frac{n}{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;
}