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
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;
stackst;
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
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];
vectork[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>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<

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<)且余数为\(0\),值得注意的一个地方就是开始读入数列中数字的时候,把每个数的数量都用\(cnt\)数组记录一下,最终的答案需要除以数字数量的全排列(全排列直接初始化,因为最大才是\(10\)),最终状态转移方程就是:

\[f[i|(1<

其中\(i\)为枚举的状态,\(j\)为放入第几位,\(k\)为余数。

代码

#include
using namespace std;
const int maxn = 15;
int f[1<>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

顺便膜拜一下机房大佬lc,下边是他的打表代码(虽然t了,但是正确率是真的优秀)这耐心和思维的缜密真是没谁了

#include
using namespace std;
typedef long long ll;
const int maxn=4e6+5;
ll ans;
map 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

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

接下来的\(M\)行,每行一个字符串,描述一个具体的操作。语法如上文所述。

输出格式

对于每一个查询操作,你应该按照顺序依次输出结果,每个结果占一行。

输入

5 100
A 96
Q 1
A 97
Q 1
Q 2

输出

96
93
96

分析

看到这些加入,修改,自然而然就想到了线段树(可惜我写的暴力,线段树调不过,这次就炸了)其实就是单点修改,区间查询,只不过在修改的时候不是取区间和,而是区间最大值,然后就是线段树的板子了。

代码

#include
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;
}

你可能感兴趣的:(6.26——集训模拟赛1【考炸的一天】)