zoukankan      html  css  js  c++  java
  • 集训小模拟赛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 eq 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
    ", 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
    ", 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
    ", 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
    ", maxans);
    		}
    	}
    }
    
    int main(){
    	solve();
    	return 0;
    }
    

    之前集训手懒一篇题解都没有写,最近不太想打新题,打算把做过的题目重新写一边题解,尽快补完吧

  • 相关阅读:
    IP地址查询接口
    Windows2008防火墙封ip
    UI设计原则
    iis7.5中使用fastcgi方式配置php5.6.5
    serv-u设置被动模式注意的问题
    mysql函数计算地表两点间距离
    vs2012出现无法启动iis express web 服务器的错误
    基于jquery的表单校验插件
    基于jquery的表单校验插件
    php redis 负载均衡[转]
  • 原文地址:https://www.cnblogs.com/hzoi-liujiahui/p/13418910.html
Copyright © 2011-2022 走看看