前言
\(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<
其中\(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;
}