前言
(Day5)了,复习正式结束,结果第一天就水到爆,下午调代码还调半天,吐了吐了。
NO.1 信息传递
题目
有 (n) 个同学(编号为 (1) 到(n) )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 (i)的同学的信息传递对象是编号为 (T_i) 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入格式
输入共(2)行。 第(1)行包含(1)个正整数 (n) ,表示 (n) 个人。
第(2)行包含 (n) 个用空格隔开的正整数 (T_1,T_2,⋯⋯,T_n)其中第 (i) 个整数 (T_i) 表示编号为 (i) 的同学的信息传递对象是编号为 (T_i) 的同学, (T_i≤n) 且 (T_i≠i) 。
数据保证游戏一定会结束。
输出格式
输出共(1)行,包含(1)个整数,表示游戏一共可以进行多少轮。
input
5
2 4 2 3 1
output
3
分析
根据题目很容易就能知道,这些数据一定是一个环状,因为问可以进行多少轮,所以肯定有一个人听到自己的信息就结束了,所以就是(tarjan)求最小环,当然,并查集,暴搜也是可行的,不过我还是觉得(tarjan)是最容易的。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
struct Node{
int v,next;
}e[maxn<<2];
int n,dfn[maxn],low[maxn],vis[maxn],tot,ans=maxn;
stack<int>st;
int head[maxn];
void add(int x,int y){
e[++tot].v = y;
e[tot].next = head[x];
head[x] = tot;
}
void tarjan(int x){//tarjan板子,求最小环
low[x]=dfn[x]=++tot;
st.push(x);
vis[x]=1;
for(int i=head[x];i;i = e[i].next){
int v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(low[x]==dfn[x]){
int cnt=0;
while(1){
int now=st.top();
st.pop();
vis[x]=0;
cnt++;
if(now==x) break;
}
if(cnt>1) ans=min(ans,cnt);
}
}
int main(){
scanf("%d",&n);
int x;
for(int i=1;i<=n;i++){//建边
scanf("%d",&x);
add(i,x);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
printf("%d
",ans);
}
NO.2 传染病控制
题目描述
近来,一种新的传染病肆虐全球。蓬莱国也发现 了零星感染者,为防止该病在蓬莱国大范围流行,该国政府决定不惜一切代价控制传染病的蔓延。不幸的是,由于人们尚未完全认识这种传染病,难以准确判别病毒 携带者,更没有研制出疫苗以保护易感人群。于是,蓬莱国的疾病控制中心决定采取切断传播途径的方法控制疾病传播。经过 (WHO)(世界卫生组织)以及全球各国科研部门的努力,这种新兴传染病的传播途径和控制方法已经研究消楚,剩下的任务就是由你协助蓬莱国疾控中心制定一个有 效的控制办法。
问题描述
研究表明,这种传染病的传播具有两种很特殊的性质;
第一是它的传播途径是树型的,一个人(X)只可能被某个特定的人(Y)感染,只要(Y)不得病,或者是(XY)之间的传播途径被切断,则X就不会得病。
第二是,这种疾病的传播有周期性,在一个疾病传播周期之内,传染病将只会感染一代患者,而不会再传播给下一代。
这些性质大大减轻了蓬莱国疾病防控的压力,并且他们已经得到了国内部分易感人群的潜在传播途径图(一棵树)。但是,麻烦还没有结束。由于蓬莱国疾控中 心人手不够,同时也缺乏强大的技术,以致他们在一个疾病传播周期内,只能设法切断一条传播途径,而没有被控制的传播途径就会引起更多的易感人群被感染(也 就是与当前已经被感染的人有传播途径相连,且连接途径没有被切断的人群)。当不可能有健康人被感染时,疾病就中止传播。所以,蓬莱国疾控中心要制定出一个 切断传播途径的顺序,以使尽量少的人被感染。你的程序要针对给定的树,找出合适的切断顺序。
输入
输入格式的第一行是两个整数(n(1≤n≤300))和(p)。接下来(p)行,每一行有两个整数(i)和(j),表示节点(i)和(j)间有边相连(意即,第i人和第j人之间有传播途径相连,注意:可能是(i)到(j)也可能是(j)到(i))。其中节点(1)是已经被感染的患者。
对于给定的输入数据,如果不切断任何传播途径,则所有人都会感染。
输出
只有一行,输出总共被感染的人数。
样例输入
7 6
1 2
1 3
2 4
2 5
3 6
7 3
样例输出
3
分析
由于树形传播,所以肯定是每一部分节点都是有深度的,所以就考虑按照深度来枚举。首先建树毫无疑问。然后进行第一次的深搜,目的是把所有的点的深度都查找出来。并且记录每一个节点的子节点个数,用(siz)数组记录。深搜结束后,把每一个深度的点都用(vector)数组存下来,然后就是第二遍深搜,在每一个深度下,枚举每个点是否被切断,枚举完毕后要更改成未切断的状态。其中(vis)数组代表的是当前点是否断开,当然,如果父节点断开,那么子节点也就全部都需要标记。如果搜到了最底层或者节点全部被标记了,那么肯定就是要记录答案(也就是最大的节点数减去被标记节点数)然后就输出就好了,具体看代码。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 305;
struct Node{//边表建树
int to,next;
}e[maxn<<2];
int ans;
int n,m;
int head[maxn],cnt;
int fa[maxn],siz[maxn],deep[maxn],madep,vis[maxn];
vector<int>k[maxn];//记录每一个深度下的所有节点
void add(int x,int y){//建树
e[++cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
void dfs(int x,int f,int dep){//第一遍深搜,找出每个节点的子树个数和深度
fa[x] = f;
siz[x] = 1;
deep[x] = dep;
madep = max(madep,dep);
for(int i=head[x];i;i = e[i].next){
if(e[i].to != f){
dfs(e[i].to,x,dep+1);
siz[x] += siz[e[i].to];
}
}
}
void Dfs(int dep,int now){//第二遍深搜,每个深度(每层)一次枚举需要断开的点,并标记求答案
if(dep == madep+1){
ans = min(now,ans);
return;
}
for(int i=0;i<k[dep].size();++i){
if(vis[fa[k[dep][i]]]){//如果父亲被标记,儿子也一定被标记
vis[k[dep][i]] = 1;
}
else vis[k[dep][i]] = 0;//未被标记
}
bool f = 1;//是否全部被标记
for(int i=0;i<k[dep].size();i++){
if(!vis[k[dep][i]])f = 0;
}
if(f){//全部被标记就记录答案跳出
ans = min(now,ans);
return;
}
for(int i=0;i<k[dep].size();++i){//枚举每一层的每个点
if(vis[k[dep][i]])continue;
vis[k[dep][i]]=1;//断开标记
Dfs(dep+1,now-siz[k[dep][i]]);//深度加一,结果减去节点子树大小
vis[k[dep][i]]=0;//重新归零,进行下一次标记其他点
}
}
int main(){
cin>>n>>m;
for(int i=1,x,y;i<=m;++i){
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs(1,0,1);
for(int i=1;i<=n;++i){
k[deep[i]].push_back(i);
}
ans = n;
Dfs(2,n);
cout<<ans;
return 0;
}
NO.3 排列perm
Description
给一个数字串(s)和正整数(d), 统计(s)有多少种不同的排列能被(d)整除(可以有前导(0))。例如(123434)有(90)种排列能被(2)整除,其中末位为(2)的有(30)种,末位为(4)的有(60)种。
Input
输入第一行是一个整数(T),表示测试数据的个数,以下每行一组(s)和(d),中间用空格隔开。(s)保证只包含数字(0, 1, 2, 3, 4, 5, 6, 7, 8, 9.)
Output
每个数据仅一行,表示能被d整除的排列的个数。
Sample Input
7
000 1
001 1
1234567890 1
123434 2
1234 7
12345 17
12345678 29
Sample Output
1
3
3628800
90
3
6
1398
HINT
在前三个例子中,排列分别有(1), (3), (3628800)种,它们都是(1)的倍数。
(100\%)的数据满足:s的长度不超过(10, 1<=d<=1000, 1<=T<=15)
分析
看到这种和谐又弱的数据范围,状压(dp)显然实锤了。然后当时我就无了思路,主要是不知道到底如何转移,考完以后听别人讲的才知道。首先(dp)当然是要有方程和(dp)数组的,我们定义一个(dp[i][j]),代表状态为(i)时,余数为(j)的方案数。我们从第一位逐一向后枚举,让每一位都填入一个数,并且让当前的余数(j)进一位也就是乘上(10)再加上我们在后边填入的那个数,最后在模上(mod),这就是一个状态转移。最后的答案就是状态为全部填入(如果数列长度为(len)的话,那么全部填入的状态则是((1<<len)-1))且余数为(0),值得注意的一个地方就是开始读入数列中数字的时候,把每个数的数量都用(cnt)数组记录一下,最终的答案需要除以数字数量的全排列(全排列直接初始化,因为最大才是(10)),最终状态转移方程就是:
其中(i)为枚举的状态,(j)为放入第几位,(k)为余数。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 15;
int f[1<<maxn][1100];
int a[maxn];
char s[maxn];
int cnt[maxn];
int T,d;
int J[]={0,1,2,6,24,120,720,5040,40320,362880,3628800};//初始化1到10的全排列
int main(){
cin>>T;
while(T--){
memset(cnt,0,sizeof(cnt));
memset(f,0,sizeof(f));
cin>>s>>d;
int len = strlen(s);
for(int i=0;i<len;++i){
a[i+1] = s[i] - '0';
cnt[a[i+1]]++;//预处理数字为a[i+1]的个数
}
f[0][0] = 1;
int lim = (1<<len);
for(int i=0;i<lim;++i){//枚举状态
for(int k=0;k<d;++k){//枚举余数
if(f[i][k])//优化效率,0的时候就不用加了
for(int j=0;j<len;++j){//枚举放第几位
if((i & (1<<j)) == 0){
f[i|(1<<j)][(k*10+a[j+1])%d] += f[i][k];//状态转移
}
}
}
}
int ans = f[lim-1][0];//答案就是状态为全部填入,余数为0的情况数
for(int i=0;i<=9;++i){
if(cnt[i]){
ans /= J[cnt[i]];//答案除以有几个同样数字的全排列,因为数字一样的话不管哪个放在一个位置都是一种
}
}
cout<<ans<<endl;
}
}
顺便膜拜一下机房大佬lc,下边是他的打表代码(虽然t了,但是正确率是真的优秀)这耐心和思维的缜密真是没谁了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=4e6+5;
ll ans;
map<ll,ll> mp;
int main(){
int t;
scanf("%d",&t);
while(t--){
mp.clear();
ans=0;
char s[50];
ll xx;
scanf("%s",s);
scanf("%lld",&xx);
int len=strlen(s);
if(len==1){
ll now=s[0]-'0';
if(now%xx==0) printf("1
");
else printf("0
");
} else if(len==2){
for(int i=0;i<=1;i++){
for(int j=0;j<=1;j++){
if(j==i) continue;
ll now=(s[i]-'0')*10ll+(s[j]-'0');
if(mp[now]==1) continue;
mp[now]=1;
if(now%xx==0) ans++;
}
}
printf("%lld
",ans);
} else if(len==3){
for(int i=0;i<len;i++){
for(int j=0;j<len;j++){
if(j==i) continue;
for(int k=0;k<len;k++){
if(k==j || k==i) continue;
ll now=(s[i]-'0')*100ll+(s[j]-'0')*10ll+(s[k]-'0');
if(mp[now]==1) continue;
mp[now]=1;
if(now%xx==0) {
ans++;
}
}
}
}
printf("%lld
",ans);
} else if(len==4){
for(int i1=0;i1<len;i1++){
for(int i2=0;i2<len;i2++){
if(i1==i2) continue;
for(int i3=0;i3<len;i3++){
if(i3==i1 || i3==i2) continue;
for(int i4=0;i4<len;i4++){
if(i4==i1 || i4==i2 || i4==i3) continue;
ll now=(s[i1]-'0')*1000ll+(s[i2]-'0')*100ll+(s[i3]-'0')*10ll+(s[i4]-'0');
if(mp[now]==1) continue;
mp[now]=1;
if(now%xx==0){
ans++;
}
}
}
}
}
printf("%lld
",ans);
} else if(len==5){
for(int i1=0;i1<len;i1++){
for(int i2=0;i2<len;i2++){
if(i1==i2) continue;
for(int i3=0;i3<len;i3++){
if(i3==i1 || i3==i2) continue;
for(int i4=0;i4<len;i4++){
if(i4==i1 || i4==i2 || i4==i3) continue;
for(int i5=0;i5<len;i5++){
if(i5==i1 || i5==i2 || i5==i3 || i5==i4) continue;
ll now=(s[i1]-'0')*10000ll+(s[i2]-'0')*1000ll+(s[i3]-'0')*100ll+(s[i4]-'0')*10ll+(s[i5]-'0');
if(mp[now]==1) continue;
mp[now]=1;
if(now%xx==0){
ans++;
}
}
}
}
}
}
printf("%lld
",ans);
} else if(len==6){
for(int i1=0;i1<len;i1++){
for(int i2=0;i2<len;i2++){
if(i1==i2) continue;
for(int i3=0;i3<len;i3++){
if(i3==i1 || i3==i2) continue;
for(int i4=0;i4<len;i4++){
if(i4==i1 || i4==i2 || i4==i3) continue;
for(int i5=0;i5<len;i5++){
if(i5==i1 || i5==i2 || i5==i3 || i5==i4) continue;
for(int i6=0;i6<len;i6++){
if(i6==i1 || i6==i2 || i6==i3 || i6==i4 || i6==i5) continue;
ll now=(s[i1]-'0')*100000ll+(s[i2]-'0')*10000ll+(s[i3]-'0')*1000ll+(s[i4]-'0')*100ll;
now+=(s[i5]-'0')*10ll+(s[i6]-'0');
if(mp[now]==1) continue;
mp[now]=1;
if(now%xx==0){
ans++;
}
}
}
}
}
}
}
printf("%lld
",ans);
} else if(len==7){
for(int i1=0;i1<len;i1++){
for(int i2=0;i2<len;i2++){
if(i1==i2) continue;
for(int i3=0;i3<len;i3++){
if(i3==i1 || i3==i2) continue;
for(int i4=0;i4<len;i4++){
if(i4==i1 || i4==i2 || i4==i3) continue;
for(int i5=0;i5<len;i5++){
if(i5==i1 || i5==i2 || i5==i3 || i5==i4) continue;
for(int i6=0;i6<len;i6++){
if(i6==i1 || i6==i2 || i6==i3 || i6==i4 || i6==i5) continue;
for(int i7=0;i7<len;i7++){
if(i7==i1 || i7==i2 || i7==i3 || i7==i4 || i7==i5 || i7==i6) continue;
ll now=(s[i1]-'0')*1000000ll+(s[i2]-'0')*100000ll+(s[i3]-'0')*10000ll+(s[i4]-'0')*1000ll;
now+=(s[i5]-'0')*100ll+(s[i6]-'0')*10ll+(s[i7]-'0');
if(mp[now]==1) continue;
mp[now]=1;
if(now%xx==0){
ans++;
}
}
}
}
}
}
}
}
printf("%lld
",ans);
} else if(len==8){
for(int i1=0;i1<len;i1++){
for(int i2=0;i2<len;i2++){
if(i1==i2) continue;
for(int i3=0;i3<len;i3++){
if(i3==i1 || i3==i2) continue;
for(int i4=0;i4<len;i4++){
if(i4==i1 || i4==i2 || i4==i3) continue;
for(int i5=0;i5<len;i5++){
if(i5==i1 || i5==i2 || i5==i3 || i5==i4) continue;
for(int i6=0;i6<len;i6++){
if(i6==i1 || i6==i2 || i6==i3 || i6==i4 || i6==i5) continue;
for(int i7=0;i7<len;i7++){
if(i7==i1 || i7==i2 || i7==i3 || i7==i4 || i7==i5 || i7==i6) continue;
for(int i8=0;i8<len;i8++){
if(i8==i1 || i8==i2 || i8==i3 || i8==i4 || i8==i5 || i8==i6 || i8==i7) continue;
ll now=(s[i1]-'0')*10000000ll+(s[i2]-'0')*1000000ll+(s[i3]-'0')*100000ll+(s[i4]-'0')*10000ll;
now+=(s[i5]-'0')*1000ll+(s[i6]-'0')*100ll+(s[i7]-'0')*10ll+(s[i8]-'0');
if(mp[now]==1) continue;
mp[now]=1;
if(now%xx==0){
ans++;
}
}
}
}
}
}
}
}
}
printf("%lld
",ans);
} else if(len==9){
for(int i1=0;i1<len;i1++){
for(int i2=0;i2<len;i2++){
if(i1==i2) continue;
for(int i3=0;i3<len;i3++){
if(i3==i1 || i3==i2) continue;
for(int i4=0;i4<len;i4++){
if(i4==i1 || i4==i2 || i4==i3) continue;
for(int i5=0;i5<len;i5++){
if(i5==i1 || i5==i2 || i5==i3 || i5==i4) continue;
for(int i6=0;i6<len;i6++){
if(i6==i1 || i6==i2 || i6==i3 || i6==i4 || i6==i5) continue;
for(int i7=0;i7<len;i7++){
if(i7==i1 || i7==i2 || i7==i3 || i7==i4 || i7==i5 || i7==i6) continue;
for(int i8=0;i8<len;i8++){
if(i8==i1 || i8==i2 || i8==i3 || i8==i4 || i8==i5 || i8==i6 || i8==i7) continue;
for(int i9=0;i9<len;i9++){
if(i9==i1 || i9==i2 || i9==i3 || i9==i4 || i9==i5 || i9==i6 || i9==i7 || i9==i8) continue;
ll now=(s[i1]-'0')*100000000ll+(s[i2]-'0')*10000000ll+(s[i3]-'0')*1000000ll+(s[i4]-'0')*100000ll;
now+=(s[i5]-'0')*10000ll+(s[i6]-'0')*1000ll+(s[i7]-'0')*100ll+(s[i8]-'0')*10ll+(s[i9]-'0');
if(mp[now]==1) continue;
mp[now]=1;
if(now%xx==0){
ans++;
}
}
}
}
}
}
}
}
}
}
printf("%lld
",ans);
} else {
for(int i1=0;i1<len;i1++){
for(int i2=0;i2<len;i2++){
if(i1==i2) continue;
for(int i3=0;i3<len;i3++){
if(i3==i1 || i3==i2) continue;
for(int i4=0;i4<len;i4++){
if(i4==i1 || i4==i2 || i4==i3) continue;
for(int i5=0;i5<len;i5++){
if(i5==i1 || i5==i2 || i5==i3 || i5==i4) continue;
for(int i6=0;i6<len;i6++){
if(i6==i1 || i6==i2 || i6==i3 || i6==i4 || i6==i5) continue;
for(int i7=0;i7<len;i7++){
if(i7==i1 || i7==i2 || i7==i3 || i7==i4 || i7==i5 || i7==i6) continue;
for(int i8=0;i8<len;i8++){
if(i8==i1 || i8==i2 || i8==i3 || i8==i4 || i8==i5 || i8==i6 || i8==i7) continue;
for(int i9=0;i9<len;i9++){
if(i9==i1 || i9==i2 || i9==i3 || i9==i4 || i9==i5 || i9==i6 || i9==i7 || i9==i8) continue;
for(int i10=0;i10<len;i10++){
if(i10==i1 || i10==i2 || i10==i3 || i10==i4 || i10==i5 || i10==i6 || i10==i7) continue;
if( i10==i8 || i10==i9) continue;
ll now=(s[i1]-'0')*1000000000ll+(s[i2]-'0')*100000000ll+(s[i3]-'0')*10000000ll;
now+=(s[i4]-'0')*1000000ll;
now+=(s[i5]-'0')*100000ll+(s[i6]-'0')*10000ll+(s[i7]-'0')*1000ll+(s[i8]-'0')*100ll;
now+=(s[i9]-'0')*10ll+(s[i10]-'0');
if(mp[now]==1) continue;
mp[now]=1;
if(now%xx==0){
ans++;
}
}
}
}
}
}
}
}
}
}
}
printf("%lld
",ans);
}
}
return 0;
}
NO.4 最大数
题目描述
现在请求你维护一个数列,要求提供以下两种操作:
1、 查询操作。
语法:(Q L)
功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值。
限制:(L)不超过当前数列的长度。((L>0))
2、 插入操作。
语法:(A n)
功能:将(n)加上(t),其中(t)是最近一次查询操作的答案(如果还未执行过查询操作,则(t=0)),并将所得结果对一个固定的常数(D)取模,将所得答案插入到数列的末尾。
限制:(n)是整数(可能为负数)并且在长整范围内。
注意:初始时数列是空的,没有一个数。
输入格式
第一行两个整数,(M)和(D),其中(M)表示操作的个数((M≤200,000)),(D)如上文中所述,满足((0<D<2,000,000,000))
接下来的(M)行,每行一个字符串,描述一个具体的操作。语法如上文所述。
输出格式
对于每一个查询操作,你应该按照顺序依次输出结果,每个结果占一行。
输入
5 100
A 96
Q 1
A 97
Q 1
Q 2
输出
96
93
96
分析
看到这些加入,修改,自然而然就想到了线段树(可惜我写的暴力,线段树调不过,这次就炸了)其实就是单点修改,区间查询,只不过在修改的时候不是取区间和,而是区间最大值,然后就是线段树的板子了。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll modd,m,tt,line;
ll a[2000001];//记录区间最大值
ll Find(ll p,ll l,ll r,ll nl,ll nr){//区间查询
if (nl<=l&&r<=nr)return a[p];
ll mid=(l+r)/2;
ll minn=-0x3ffffffff;
if (nl<=mid)minn=Find(p*2,l,mid,nl,nr);
if (nr>mid)minn=max(minn,Find(p*2+1,mid+1,r,nl,nr));
return minn;
}
void Add(ll p,ll l,ll r,ll w,ll v){//单点修改
if(l==r){a[p]=v;return;}
ll mid=(l+r)/2;
if(w<=mid)Add(p*2,l,mid,w,v);
else Add(p*2+1,mid+1,r,w,v);
a[p]=max(a[p*2],a[p*2+1]);
}
int main(){
cin>>m>>modd;
for(int i=1;i<=m;i++){
char s;
cin>>s;
if(s=='A'){
ll ls;
cin>>ls;
line++;
Add(1,1,m,line,(tt+ls)%modd);
}
else {
ll ls;
cin>>ls;
if(!ls)tt=0;
else
tt=Find(1,1,m,line-ls+1,m);//记录上一次查询的答案
printf("%lld
",tt);
}
}
return 0;
}