6.26——集训模拟赛1【考炸的一天】
前言
\(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\n",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=300+5,Inf=2147483647;
int n,m,max_dis,ans=2147483647;
int dis[maxn],head[maxn],vis[maxn],f[maxn],deep[maxn][maxn],cnt[maxn];
struct Edge{
int next,to;
}e[maxn<<1];
void Add(int u,int v){
e[++head[0]].to=v;
e[head[0]].next=head[u];
head[u]=head[0];
}
void dfs(int now,int fa){
for(int i=head[now];i;i=e[i].next){
int v=e[i].to;
if(v==fa) continue;
dis[v]=dis[now]+1;
f[v]=now;
max_dis= max(max_dis,dis[v]);
dfs(v,now);
}
}
void tag(int u,int lt){
vis[u]=lt;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==f[u]) continue;
vis[v]=lt;
tag(v,lt);
}
}
int Sum(int dep){
int sum=0;
for(int i=1;i<=cnt[dep];i++)
if(vis[deep[dep][i]]==0)
sum++;
return sum;
}
void Search(int dep,int sum){
if(sum>=ans) return;
if(dep>max_dis|| Sum(dep)==0){
ans= min(ans,sum);
return;
}
for(int i=1;i<=cnt[dep];i++){
int to=deep[dep][i];
if(vis[to]==1) continue;
tag(to,1);
Search(dep+1,sum+ Sum(dep));
tag(to,0);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;scanf("%d%d",&u,&v);
Add(u,v);Add(v,u);
}
dfs(1,0);
for(int i=1;i<=n;i++)
deep[dis[i]][++cnt[dis[i]]]=i;
Search(1,1);
printf("%d\n",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\n");
else printf("0\n");
} 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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",tt);
}
}
return 0;
}