集训小模拟赛1
A.信息传递
题目描述:
有n个同学(编号为1到n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入格式
输入共2行。
第1行包含1个正整数n表示n个人。
第2行包含n个用空格隔开的正整数\(T1,T2...Tn\),其中第i个整数Ti表示编号为i的同学的信息传递对象是编号为Ti的同学, 且\(Ti \leq\) n$ 且 \(Ti \neq i\),数据保证游戏一定会结束。
输出格式
输出共 1 行,包含 1 个整数,表示游戏一共可以进行多少轮。
样例
\(input\)
5
2 4 2 3 1
3
思路
很简单的一道并查集的题目,直接上代码,话不多说,脚写都没问题。。。
代码实现
#include <cstdio>
#include <iostream>
using namespace std;
const int maxn = 200010;
int fa[maxn], m[maxn], n, last;
int minans = 0x3f3f3f3f;
int a;
int find_root(int x) {
if (fa[x] != x) {
int last = fa[x];
fa[x] = find_root(fa[x]);
m[x] += m[last];
}
return fa[x];
}
void find(int a, int b) {
int x = find_root(a), y = find_root(b);
if (x != y) {
fa[x] = y;
m[a] = m[b] + 1;
} else
minans = min(minans, m[a] + m[b] + 1);
return;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
for (int i = 1; i <= n; i++) {
scanf("%d", &a);
find(i, a);
}
printf("%d\n", minans);
return 0;
}
B.传染病控制
题目描述
近来,一种新的传染病肆虐全球。蓬莱国也发现了零星感染者,为防止该病在蓬莱国大范围流行,该国政府 决定不惜一切代价控制传染病的蔓延。不幸的是,由于人们尚未完全认识这种传染病,难以准确判别病毒携带者,更没有研制出疫苗以保护易感人群。于是,蓬莱国 的疾病控制中心决定采取切断传播途径的方法控制疾病传播。经过 WHO (世界卫生组织)以及全球各国科研部门的努力,这种新兴传染病的传播途径和控制方法已经研究消除,剩下的任务就是由你协助蓬莱国疾控中心制定一个有效的控 制办法。
研究表明,这种传染病的传播具有两种很特殊的性质;
第一是它的 传播途径是树型的,一个人\(X\)只可能被某个特定的人\(Y\)感染,只要\(Y\)不得病,或者是\(X,Y\)之间的传播途径被切断,则\(X\)就不会得病。
第二是,这种疾病的传播有周期性,在一个疾病传播周期之内,传染病将只会感染一代患者,而不会再传播给下一代。
这些性质大大减轻了蓬莱国疾病防控的压力,并且他们已经得到了国内部分易感人群的潜在传播途径图(一棵树)。但是,麻烦还没有结束。由于蓬莱国疾控中心人手 不够,同时也缺乏强大的技术,以致他们在一个疾病传播周期内,只能设法切断一条传播途径,而没有被控制的传播途径就会引起更多的易感人群被感染(也就是与 当前已经被感染的人有传播途径相连,且连接途径没有被切断的人群)。当不可能有健康人被感染时,疾病就中止传播。所以,蓬莱国疾控中心要制定出一个切断传 播途径的顺序,以使尽量少的人被感染。
你的程序要针对给定的树,找出合适的切断顺序。
输入格式
输入格式的第一行是两个整数\(n(0 \leq n \leq 100)\)和\(p\)。
接下来p行,每一行有两个整数i和j,表示节点i和j间有边相连(意即,第i人和第j人之间有传播途径相连,可相互感染)。其中节点1是已经被感染的患者。
样例
\(input\)
7 6
1 2
1 3
2 4
2 5
3 6
3 7
\(output\)
3
思路
大致实现都写在了注释里面,不多写了
代码实现
#include <bits/stdc++.h>
const int maxn = 300 + 5, Inf = 2147483647;
int n, m, max_dis, ans = 2147483647;
int dis[maxn], head[maxn], Cut[maxn], f[maxn], deep[maxn][maxn], cnt[maxn];
struct Edge {
int next, to;
} e[maxn << 1];
void Insert(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 = std::max(max_dis, dis[v]);
dfs(v, now);
}
}
void tag(int u, int color) { //把以u为根的子树都打上隔断标记
Cut[u] = color;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (v == f[u])
continue;
Cut[v] = color;
tag(v, color);
}
}
int calc(int dep) { //计算dep层感染人数
int sum = 0;
for (int i = 1; i <= cnt[dep]; i++)
if (Cut[deep[dep][i]] == 0)
sum++;
return sum;
}
void Search(int dep, int sum) { // sum到dep层时已感染的人数
if (sum >= ans)
return;
if (dep > max_dis || calc(dep) == 0) { //当前层无感染或超过了最大深度
ans = std::min(ans, sum);
return;
}
for (int i = 1; i <= cnt[dep]; i++) { //枚举dep层的每一个点
int to = deep[dep][i];
if (Cut[to] == 1)
continue; // Cut[i]==1表示i已经被隔断,不会被传染
tag(to, 1); //隔断to和to的子树
Search(dep + 1, sum + calc(dep)); //爆搜下一层
tag(to, 0); //取消对to和to子树的隔断标记
}
}
void Solve() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
Insert(u, v);
Insert(v, u);
}
dfs(1, 0); //预处理出点到根几点的距离和点的父亲节点
for (int i = 1; i <= n; i++) // deep[i][j]:第i层的第j个点
deep[dis[i]][++cnt[dis[i]]] = i; // cnt[i]:第i层的节点个数
Search(1, 1); //逐层爆搜
printf("%d\n", ans);
}
int main() {
Solve();
return 0;
}
C.排列
题目描述
给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0)。
例如123434有90种排列能被2整除,其中末位为2的有30种,末位为4的有60种。
输入格式
输入第一行是一个整数T,表示测试数据的个数,以下每行一组s和d,中间用空格隔开。s保证只包含数字0,1,2,3,4,5,6,7,8,9。
输出格式
每个数据仅一行,表示能被d整除的排列的个数。
样例
\(input\)
7
000 1
001 1
1234567890 1
123434 2
1234 7
12345 17
12345678 29
\(output\)
1
3
3628800
90
3
6
1398
思路
看数据范围很弱,显然可以用状压dp求解,同时也可以用全排列搞,即暴力枚举出所有情况。
思路1(状压dp)
#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;
}
}
思路2(dfs枚举)
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int Maxn = 20;
char s[Maxn];
int d, num[Maxn];
void get_num() {
num[0] = strlen(s);
for (register int i = 1; i <= num[0]; i++) {
num[i] = s[i - 1] - '0';
}
}
ll dfs(int pre, int s, ll tot) {
if (pre == num[0])
return tot % d == 0;
bool vis[Maxn];
for (int i = 0; i <= 10; i++) vis[i] = 0;
ll t = 0;
for (int i = 1; i <= num[0]; i++) {
if (vis[num[i]] || (s & 1 << i - 1))
continue;
vis[num[i]] = 1;
t += dfs(pre + 1, s | 1 << i - 1, tot * 10 + num[i]);
}
return t;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%s%d", s, &d);
get_num();
printf("%lld\n", dfs(0, 0, 0));
}
}
D.最大数
题目描述
现在请求你维护一个数列,要求提供以下两种操作:
1.查询操作。
语法:Q L
功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值。
限制:L不超过当前数列的长度。
2.插入操作。
语法: A n
功能:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t = 0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列的末尾。
限制:n是非负整数并且在长整范围内。
注意:初始时数列是空的,没有一个数。
输入格式
第一行两个整数,M和D,其中M表示操作的个数(M \leq 200000),D如上文中所述,满足(0 \ leq D \ leq 2000000000)
接下来的M行,每行一个字符串,描述一个具体的操作。语法如上文所述。
输出格式
对于每一个查询操作,你应该按照顺序依次输出结果,每个结果占一行。
样例
\(input\)
5 100
A 96
Q 1
A 97
Q 1
Q 2
\(output\)
96
93
96
思路
现在看之前集训的题目真的好水啊23333,这不就是个裸裸的线段树吗。。感觉没啥可说的了,直接上代码(逃)
代码实现
#include<bits/stdc++.h>
using namespace std;
int tree[200050 << 2], a[200050], M, D;
int ad, check;
char c;
void modify(int rt, int l, int r, int x, int y){
if(l == r){
tree[rt] += y;
return;
}
int mid = (l + r) >> 1;
if(x <= mid) modify(rt << 1, l, mid, x, y);
else modify(rt << 1 | 1, mid+1, r, x, y);
tree[rt] = max(tree[rt << 1], tree[rt << 1 | 1]);
}
int query(int rt, int l, int r, int s, int t){
if(s <= l && t >= r){
return tree[rt];
}
int mid = (l + r) >> 1;
if(t <= mid) return query(rt << 1, l, mid, s, t);
else if(s > mid) return query(rt << 1 | 1, mid + 1, r, s, t);
else return max(query(rt << 1, l, mid, s, t), query(rt << 1 | 1, mid+1, r, s, t));
}
void solve(){
int maxans = 0;
int len = 0;
scanf("%d%d", &M, &D);
for(int i = 1; i <= M; i++){
cin>>c;
if(c == 'A'){
scanf("%d", &ad);
modify(1, 1, 200000, ++len, (maxans + ad) % D);
}else{
scanf("%d", &check);
maxans = query(1, 1, 200000, len - check + 1, len);
printf("%d\n", maxans);
}
}
}
int main(){
solve();
return 0;
}
之前集训手懒一篇题解都没有写,最近不太想打新题,打算把做过的题目重新写一边题解,尽快补完吧