T1. 重建(rebuild)
A国拥有N个城市编号为0~N-1,这N个城市由N条双向道路所连接形成了一个环。环上城市的编号沿顺时针方向递增(除第N-1号城市外)。
不幸的是,A国遭遇了一场地震,摧毁了所有N个城市的电力系统。在地震后的K天中,第i天会由城市C_i派出一支重建队伍,他们从第C_i个城市出发,找到顺时针方向上第一个电力系统未被修复的城市并修复它。在该城市电力系统被修复后,所有与它有联系的城市将会向它提供1吨援助物资。城市A与城市B有联系仅当A与B是同一个城市,或者城市A可以沿着道路在只经过电力系统被修复的城市的情况下到达城市B。
请你编写一个程序帮助A国统计在K天的重建过程中的援助物资总量。
输入
一行三个整数N,K,C_1
C_2 到C_K 可以由以下公式计算
C[i]=(C[i-1]+6655)*1551%N
输出
输出一个整数,表示重建过程中的援助物资总量
样例输入
6 4 0
样例输出
6
数据规模
对于40%的数据1≤ K < N ≤1000
对于100%的数据1≤ K < N ≤3000000
时限2s,空间256MB
PS :此题数据我已经上传至codevs,题号:6704 传送门:http://codevs.cn/problem/6704/
.
.
题解:
我们先看一下官方题解
我们可以使用并查集来维护两个额外信息
1.每个城市所在联通块的大小
2.每个城市所在联通块顺时针方向上第一个未被修复的城市
第一个信息可以在合并时直接去维护,第二个信息通过每次合并时以顺时针方向的城市为根来维护。
每次操作时先通过所在并查集的根找到实际被修复城市进行合并,然后将联通块大小累加进答案。如果将并查集复杂度视作O(1),则总复杂度为线性的。
.
.
.
.
个人解法:我说一下我的做法,并查集是妥妥要用的,但是这题蒟蒻我并查集用的比较巧妙吧.
其实这题呢,一个施工队一开始先自己修理自己是等价于全部都一开始修理按顺时针走的下一个城市的,我记录父亲节点的数组初始化为当前节点按顺时针下去的第一个节点,这样当前节点父节点就是当前城市要修理的下一个城市。
然后重点来了,这样打的并查集在父亲函数里要加上一句,判断当前这个点是否被修过,如果没被修理过,返回此节点的编号。
#include<iostream>
#include<cstdio>
#define LL long long
using namespace std;
LL n,k,c,ans=0,num=0,x,y;
int fa[10000000],size[10000000];
bool have[10000000];
int find(int i)
{
if(have[i]==0) return i;
fa[i]=find(fa[i]);
return fa[i];
}
int main()
{
cin>>n>>k>>c;
for(int i=0;i<n;i++)
{
fa[i]=(i+1)%n;
size[i]=0;
have[i]=false;
}
while(k--)
{
x=find(fa[c]);
int xx=x;
num++;
if(num==n)
{
ans+=n;
break;
}
have[x]=true;
size[find(fa[x])]++;
size[find(fa[x])]+=size[xx];
ans+=size[find(fa[x])];
c=(c+6655%n)*1551%n;
}
cout<<ans;
return 0;
}
.
.
.
.
.
.
2.字符串(zero)
给你一个只包含“0”与“1”的字符串S,你可以对这个字符串进行操作,每次操作将字符串中任意一个字符移动到字符串的任意位置,例如S=“0010”’,你可以将首位的0移动到末尾位置,使得S=“0100”。
现在有Q次询问,每次询问给出一个操作次数上限K,每次询问你需要回答在对字符串S进行不超过K次操作后,字符串最长连续“0”的长度。
输入
第一行一个01字符串S
第二行一个整数Q,代表询问次数
接下来Q行,每行一个数字K_i
输出
输出Q行,每行一个整数,代表对应询问中字符串最长连续“0”的长度
样例输入
0000110000111110
5
1
2
3
4
5
样例输出
5
8
9
9
9
数据规模
对于30%的数据 ,字符串长度为
对于100%的数据
时限2s,空间256MB
官方题解:ans=max{r-l-(sum[r]-sum[l])+k-(sum[r]-sum[l])| sum[r]-sum[l]<=k}
sum是前缀和(表示前i个数中有几个1),式子表示将[l,r]中的1移出去,[l,r]外的0移进来。
固定右端点r,对左端点2*sum[l]-l做单调队列 ,复杂度O(n*q)。
k>=sum[n]的时候特判一下。
.
.
.
个人解法:其实官方题解基本讲完了,我就解释一下官方题解里的一些东西吧。
ans=max{r-l-(sum[r]-sum[l])+k-(sum[r]-sum[l])| sum[r]-sum[l]<=k} 里面, r-l 表示[ l , r ]区间长度,即此区间的数字个数。-(sum[r]-sum[l])表示了吧这个区间中的1全部移开,+k-(sum[r]-sum[l])表示当把区间中1全部移开后,再把0移进来的数量。sum[r]-sum[l]<=k表示如果操作次数比这个区间的1的个数小,就没必要算了。我们可以对于每个询问,都暴力扫一次文本串,复杂度O(n*n);
.
.
.
优化:上面的 r-l-(sum[r]-sum[l])+k-(sum[r]-sum[l])可以化为
(r+k-2 * sum[r]) + (2 * sum[l]-l)
当r固定时,最大值取决于 (2 * sum[l]-l),用个单调队列优化一下,对于每个询问还是暴力扫,时间复杂度为O(n*q)。
暴力版程序:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
using namespace std;
queue<int>dui;
char a[1000000];
int sum[10000000];//记录1的个数
LL q,n,m,len,ans=0,k,x,y,h=0;
int main()
{
cin>>a;
int len=strlen(a);
sum[0]=0;
for(int i=1;i<=len;i++)
{
if(a[i-1]=='1')
sum[i]=sum[i-1]+1;
else sum[i]=sum[i-1];
}
h=len-sum[len];
cin>>q;
while(q--)
{
cin>>k;
ans=0;
for(int r=0;r<=len;r++)
{
for(int l=0;l<r;l++)
{
if(sum[r]-sum[l]<=k)
{
m=r-l+k-2*sum[r]+2*sum[l];
if(m>h) ans=h;
else ans=max(ans,m);
}
}
}
cout<<ans<<endl;;
}
return 0;
}
.
.
.
用单调队列优化后的正解程序
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
using namespace std;
char a[1000000];
int sum[10000000];//记录1的个数
struct node
{
int l,val;
}dui[100000];//手动模拟队列
LL q,n,m,len,ans=0,k,x,y,h=0,head,tail,l,test;
int main()
{
cin>>a;
int len=strlen(a);
sum[0]=0;
for(int i=1;i<=len;i++)
{
if(a[i-1]=='1')
sum[i]=sum[i-1]+1;
else sum[i]=sum[i-1];
}
h=len-sum[len];
cin>>q;
while(q--)
{
cin>>k;
head=1,tail=0;
memset(dui,0,sizeof(dui));
ans=0;
for(int r=0;r<=len;r++)
{
l=r;
m=2*sum[l]-l;
while(head<=tail&&m>=dui[tail].val)
tail--;
dui[++tail].val=m;
dui[tail].l=l;
while(head<=tail&&sum[r]-sum[dui[head].l]>k)
head++;
m=r+k-2*sum[r]+dui[head].val;
if(m>h) ans=h;
else ans=max(ans,m);
}
cout<<ans<<endl;
}
return 0;
}