zoukankan      html  css  js  c++  java
  • 2019 ICPC Shanghai Regional Contest

    6/13
    剩下的先鸽了,以后再补~

    A Mr. Panda and Dominoes

    B Prefix Code

    大意:

    给出n个字符串,问对于每一个字符串,是否是其他字符串的前缀,是的话输出No

    思路:

    对于每个字符串,取出它的前缀加入map中,最后判断每个字符串对应的值是否大于等于2即可

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    int t, n, k, flag;
    
    string s[N];
    int main(){
        scanf("%d", &t);
    
        while(t--){
            k++;
            flag = 1;
            scanf("%d", &n);
    
            unordered_map<string,int> mp;
            for (int i = 0; i < n;i++){
                cin >> s[i];
                if(mp[s[i]]){
                    flag = 0;
                }
                //mp2[s[i]]++;
                string temp = "";
                for (int j = 0; j < s[i].size(); j++){
                    temp.push_back(s[i][j]);
                    mp[temp]++;  
                }
            }
            for (int i = 0; i < n;i++){
                if(mp[s[i]]>=2){
                    flag=0;
                    break;
                }
            }
            if (flag)
            {
                printf("Case #%d: Yes
    ", k);
            }
            else{
            printf("Case #%d: No
    ", k);
            }
        }
        return 0;
    }
    

    C Maze

    D Spanning Tree Removal

    大意:

    给出一个n个节点的完全图,每次删掉一个生成树的(n-1)个边,问最多能删几次,输出每次删哪些边

    思路:

    因为n个节点的完全图一共有((n-1)n/2)个边,所以一共能删(n/2)次,但是怎么删边呢?队友想出了一种方法:

    image-20201125152709665.png

    就是将1到n按照顺时针排成一圈,然后每次选择1,2,6,3,5,4这样的类似“螺旋”的线生成一个树即可

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    int t, n,cases;
    int main(){
        cin >> t;
        while(t--){
            cin >> n;
            cases++;
            int now = 1;
            map<pair<int, int>, int> mp;
            int time = n / 2;
            printf("Case #%d: %d
    ", cases, time);
            while(now<=time){
                for (int i = 0; i < (n - 1);i++){
                    if((i&1)==0){
                        int a = (now - (i / 2)+n) % (n);
                        if(a==0)
                            a = n;
                        int b = (now + (i / 2) + 1) % (n);
                        if(b==0)
                            b = n;
                        cout << a << ' ' << b << endl;
                    }
                    else{
                        int a = (now + (i / 2) + 1) % n;
                        if(a==0){
                            a = n;
                        }
                        int b = (now - (i / 2 + 1) + n) % n;
                        if(b==0){
                            b = n;
                        }
                        cout << a << ' ' << b << endl;
                    }
                }
                now++;
            }
        }
        return 0;
    }
    

    E Cave Escape

    大意:

    给出一个(n*m)的矩阵,从一个位置((i,j))可以移动到相邻的位置((x,y)),如果移动到的这个位置是第一次访问,那么得到收益为(V_{i,j}*V_{x,y}),否则为0,问从(S_r,S_c)移动到(T_r,T_c)的最大收益是多少,走到终点时,可以不结束而继续访问别的格子。

    其中,(V_{i,j})的大小由如下公式确定:

    (X_i=(A*X_{i-1}+B*X_{i-2}+C)mod(P))

    (V_{i,j}=X_{(i-1)*m+j})

    思路

    一开始被给出的公式搞晕了,以为肯定有什么深意,但是最后发现就是没啥用,只是求(V_{i,j})的公式而已....由于每个点只能贡献一次收益,所以如果我们将每个相邻的点之间连边,边权设为元素权值的积的话,可以看出就是找到一个最大生成树即可,所以跑一遍(kruskal)算法,将算法中的排序改成降序即可。

    #include<bits/stdc++.h>
    
    using namespace std;
    
    typedef long long LL;
    const int N = 2e6 + 5;
    int t, n, m, sr, sc, tr, tc,f[2][2]={0,1,1,0},num=0,p[N],cases=0;
    LL x[N], A, B, C, P,v[1005][1005];
    struct node{
        int u, v;
        LL w;
        bool operator< (const node &W)const
        {
            return w > W.w;  //重载运算符,因为是最大生成树,所以要return >
        }
    }edge[N];
    
    int find(int x)
    {
        if (x != p[x]) p[x] = find(p[x]);
        return p[x];
    }
    
    LL kruskal()
    {
        sort(edge, edge + num);  // 排序,使得从最大权值的边开始
        LL res = 0;
        int cnt = 0;                                  // res记录最大生成树的权值,cnt记录当前最大生成树内有几条边
        for (int i = 1; i <= n*m; ++i) p[i] = i;  // 并查集初始化
        for (int i = 0; i < num ; ++i)  // 枚举每一条边
        {
            int a = edge[i].u, b = edge[i].v, w = edge[i].w;  // 边a, b, 权值为w
            a = find(a), b = find(b);  // 得到a的父节点和b的父节点
            if (a != b)  // 判断a和b是否在同一个集合内
            {
                res += w;  // 在的话放入集合内
                cnt++;
                p[a] = b;
            }
        }
        return res;
    }
    
    int main(){
        cin>>t;
        while(t--){
            num = 0;
            cases++;
            cin >> n >> m >> sr >> sc >> tr >> tc;
            cin >> x[1] >> x[2] >> A >> B >> C >> P;
            for (int i = 3; i <= n*m;i++){
                x[i] = (A * x[i - 1] + B * x[i - 2] + C) % P;
            }
            for (int i = 1; i <= n;i++){
                for (int j = 1; j <= m;j++){
                    v[i][j] = x[(i - 1) * m + j];
                }
            }
            for (int i = 1; i <= n;i++){
                for (int j = 1; j <= m;j++){
                    for (int k = 0; k < 2;k++){   //给相邻的两个点连边,只需要连下方的和右边的即可,避免重复
                        int xx = i + f[0][k];
                        int yy = j + f[1][k];
                        if (xx <= n && yy <= m){
                            node temp;
                            temp.u = (i - 1) * m + j;
                            temp.v = (xx - 1) * m + yy;
                            temp.w = x[(i - 1) * m + j] * x[(xx - 1) * m + yy];
                            edge[num++]=temp;
                        }
                    }
                }
            }
            LL res = kruskal();
            printf("Case #%d: %lld
    ", cases, res);
        }
        return 0;
    }
    

    F A Simple Problem On A Tree

    题意:

    给定一颗树,每个点带点权,维护四个操作:

    1 u v w : 将 u 、v 路径上的权值修改为 w
    2 u v w : 将 u 、v 路径上的权值加上 w
    3 u v w : 将 u 、v 路径上的权值乘上 w
    4 u v : 询问 u 、v 路径上点权的立方和

    思路:

    树链剖分+线段树,维护区间立方和

    由于开的空间过大,所以不能用数组实现邻接表 直接上vector

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 1e5+5;
    typedef long long LL;
    const int p = 1e9 + 7;
    int tot, num;
    int n, m, r,t,cases=0;
    
    int w[N], a[N], sum1[N * 4], sum2[N * 4], sum3[N * 4], lazy_mul[N * 4], lazy_add[N * 4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
    //int h[N], e[N * 2], ne[N * 2], idx;   // 邻接表数组
    int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜索序 wson重儿子 size子树大小 top链头  fa父节点
    vector<int> mp[N];
    /*
    void add(int a, int b)
    {
        e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
    }*/
    
    // 得到sz, fa, dep, wson数组
    void dfs1(int u)
    {
        dep[u] = dep[fa[u]]+1; 
        sz[u] = 1; 
        for(int i = 0; i<mp[u].size(); i++)
        {
            int j=mp[u][i]; 
            if(j == fa[u]) continue; 
            fa[j] = u; 
            dfs1(j); 
            sz[u] += sz[j]; 
            if(sz[j] > sz[wson[u]]) wson[u] = j;  // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
        }
    }
    
    // 得到dfn, top数组 
    void dfs2(int u, int nowtop)
    {
        dfn[u] = ++num; 
        w[num] = a[u]; 
        //以搜索序重排权值
        top[u] = nowtop; 
        if(wson[u]) dfs2(wson[u], nowtop);   // 先搜索重儿子
        for(int i = 0; i<mp[u].size(); i++)  // 然后搜索轻儿子
        {
            int y=mp[u][i]; 
            if(y ==fa[u]||y == wson[u]) continue; 
            dfs2(y, y); 
        }
    }
    
    void solve(int rt,int len,int a,int b){   //a为add b为mul
        lazy_mul[rt] = 1ll*lazy_mul[rt] * b % p;
        lazy_add[rt] = 1ll*lazy_add[rt] * b % p;
        lazy_add[rt] = ((lazy_add[rt] + a) % p + p) % p;
        if(b!=1){   //先乘后加
            sum1[rt] = 1ll*sum1[rt] * b % p;
            sum2[rt] = (1ll*sum2[rt] * b % p) * b % p;
            sum3[rt] = ((1ll*sum3[rt] * b % p) * b % p) * b % p;
        }
        if(a!=0){
            int a2 = 1ll*a * a % p, a3 = 1ll*a2 * a % p;
            sum3[rt] = ((sum3[rt] + (LL)len * a3 % p) + p) % p;
            sum3[rt] = ((sum3[rt] + 3ll * (LL)sum2[rt] % p * a % p) + p) % p;
            sum3[rt] = ((sum3[rt] + 3ll * (LL)sum1[rt] % p * a2 % p) + p) % p;
            sum2[rt] = ((sum2[rt] + 2ll * (LL)sum1[rt] % p * a % p) + p) % p;
            sum2[rt] = ((sum2[rt] + (LL)len * a2 % p) + p) % p;
            sum1[rt] = ((sum1[rt] + (LL)len * a % p) + p) % p;
        }
    }
    
    void pushup(int rt) {
        sum1[rt] = (sum1[rt << 1] + sum1[rt << 1 | 1]) % p;
        sum2[rt] = (sum2[rt << 1] + sum2[rt << 1 | 1]) % p;
        sum3[rt] = (sum3[rt << 1] + sum3[rt << 1 | 1]) % p;
    }   
    
    // 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
    void build(int rt, int l, int r)
    {
        lazy_add[rt] = 0;
        lazy_mul[rt] = 1;
        if(l==r)
        {
            int temp = w[l];
            sum1[rt] = temp;
            sum2[rt] = (1ll*sum1[rt] * sum1[rt]) % p;
            sum3[rt] = (1ll*sum1[rt] * sum2[rt]) % p;
            return ; 
        }
        int mid=(l + r)>>1; 
        build(rt << 1, l, mid); 
        build(rt << 1 | 1, mid+1, r); 
        pushup(rt);
    }
    
    // 下传
    void pushdown(int rt, int l, int r)
    {
        int mid = (l + r) >> 1;
        solve(rt << 1, mid - l + 1, lazy_add[rt], lazy_mul[rt]);
        solve(rt << 1 | 1, r - mid, lazy_add[rt], lazy_mul[rt]);
        lazy_add[rt] = 0;
        lazy_mul[rt] = 1;
    }
    
    // rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, L为需要修改的左区间,R为需要修改的右区间
    void modify(int rt, int l, int r, int L, int R, int a,int b)
    {
        if(L <= l && r <= R)
        {
            solve(rt, r - l + 1, a, b);
            return ; 
        } 
        pushdown(rt, l, r); 
        int mid = (l + r)>>1; 
        if(L <= mid) modify(rt << 1, l, mid, L, R, a,b); 
        if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, a,b); 
        pushup(rt);
    }
    
    // rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, L为需要查询的左区间,R为查询的右区间
    int query(int rt, int l, int r, int L, int R)
    {
        if(L <= l && r <= R)
        {
            return sum3[rt]; 
        }
        pushdown(rt, l, r); 
        int mid = (l + r)>>1; 
        int ans = 0; 
        if(L <= mid) ans += query(rt << 1, l, mid, L, R), ans %= p; 
        if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R), ans %= p;
        pushup(rt);
        return ans; 
    }
    
    // 求树从 x 到 y 结点最短路径上所有节点的值之和
    int path_query_Tree(int x, int y)
    {   
        //两点间的修改
        int ans = 0; 
        while(top[x] != top[y])  // 把x点和y点整到一条重链上
        {
            if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 让x点为深的那个点
            ans += query(1, 1, n, dfn[top[x]], dfn[x]);   
            ans %= p; 
            x = fa[top[x]];  // x每次跳一条链
        }
        if(dep[x] > dep[y]) swap(x, y);   // 让x成为深度更浅的那个点
        ans += query(1, 1, n, dfn[x], dfn[y]); 
        return ans % p; 
    }
    
    // 树上修改 a为add ,b为mul
    void path_modify_Tree(int x, int y, int a,int b)
    {
        //树上两点距离 
        while(top[x] != top[y]) // 把x点和y点整到一条重链上
        {
            if(dep[top[x]] < dep[top[y]]) swap(x, y); // 让x成为对应的头部深度更大的那个点
            modify(1, 1, n, dfn[top[x]], dfn[x], a,b); // 累加x的所有子树和
            x = fa[top[x]]; // x跳到原来x的头部的父节点
        }
        if(dep[x] > dep[y]) swap(x, y);  // 让x成为深度更浅的那个点
        modify(1, 1, n, dfn[x], dfn[y], a,b); 
    }
    
    int main()
    {
        scanf("%d", &t);
        while(t--){
            scanf("%d", &n);
            cases++;
            // 读入边,建树
            for(int i=1; i<=n; ++i) mp[i].clear(),wson[i]=0;
            //memset(h,  -1,  sizeof h);
            num = 0;
            for(int i=1, x, y; i<n; i++)
            {
                scanf("%d%d", &x, &y);
                mp[x].push_back(y);
                mp[y].push_back(x); 
            }
    
            for(int i=1; i<=n; i++) scanf("%d", &a[i]); // 读入每个点的权值
            // 两次dfs把树按照重链剖分
            dfs1(1);   // 得到sz, fa, dep, wson数组
            dfs2(1, 1);   // 得到dfn, top数组
            build(1, 1, n); 
            scanf("%d", &m);  
            printf("Case #%d:
    ", cases);
            // m次询问
            for(int i=1, op, x, y, z; i<=m; i++)
            {
                scanf("%d", &op); 
                if(op == 1)
                {
                    scanf("%d%d%d", &x, &y, &z); 
                    path_modify_Tree(x, y, z,0); // 将树从 x到 y 结点最短路径上所有节点的值赋值为z
                }
                else if(op == 2)
                {
                    scanf("%d%d%d", &x, &y, &z); 
                    path_modify_Tree(x, y, z,1); // 将树从 x到 y 结点最短路径上所有节点的值加上z
                }
                else if(op == 3)
                {
                    scanf("%d%d%d", &x, &y, &z); 
                    path_modify_Tree(x, y, 0,z); // 将树从 x到 y 结点最短路径上所有节点的值乘上z
                }
                else 
                {
                    scanf("%d%d", &x,&y); 
                    printf("%d
    ", path_query_Tree(x,y)); 
                }
            }
        } 
        
    
        return 0;
    }
    

    G Play the game SET

    H Tree Partition

    题意:

    给出一棵树,切k-1刀分成k棵树,问这k棵树里最大的权值的最小值是多少,其中权值是整棵树上点的权值之和

    思路:

    对于最小的最大值这种类型的问题,应该很快想到二分法,那么怎么判断当前的mid是否是答案呢?可以从叶子节点开始搜索,如果当前节点及其子树的权值大于mid,则切去最大的子树,最后判断生成多少个子树,如果小于k,则代表当前二分到的mid值大了,应该减小,反之mid应该增加

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N = 2e5 + 5;
    typedef long long LL;
    int n, k, t,cnt,cases=0;
    
    LL w[N], mid, wt[N];
    
    vector<int> mp[N];
    
    vector<LL>son[N];
    
    void dfs(int now,int pre){
        int sonnum = 0;
        wt[now] = w[now];
        son[now].clear();
        for (int i = 0; i < mp[now].size();i++){
            int next = mp[now][i];
            if(next==pre)
                continue;
            dfs(next,now);
            son[now].push_back( wt[next]);
            sonnum++;
            wt[now] += wt[next];
        }
        sort(son[now].begin(), son[now].end());
        while(wt[now]> mid&&(sonnum>=1)){
            wt[now] -= son[now][sonnum - 1];
            sonnum--;
            cnt++;
        }
    }
    
    int main(){
        cin >> t;
        while(t--){
            cin >> n>>k;
            cases++;
            for (int i = 1; i <= n;i++){
                mp[i].clear();
            }
            for (int i = 0; i < n - 1; i++)
            {
                int a, b;
                cin >> a >> b;
                mp[a].push_back(b);
                mp[b].push_back(a);
            }
            LL l = 0, r = 0;
            for (int i = 1; i <= n;i++){
                cin >> w[i];
                l = max(l, w[i]);
                r += w[i];
            }
            
            while(l<r){   //二分找符合条件的最小值
                mid = (l + r) >> 1;
                cnt = 1;
                dfs(1,0);
                if(cnt<=k)
                    r = mid;
                else
                    l = mid + 1;
            }
            printf("Case #%d: %lld
    ", cases, l);
        }
        return 0;
    }
    

    I Portal

    J Bob's Poor Math

    K Color Graph

    题意:

    一个n个点m条边的无向图,给图中的边染色,不允许存在所有边被染色的奇环,求最大的染色数。

    思路:

    一个无奇环的图必定是一个二分图,所以只需要找一个最大边数的二分图就行了。

    由于数据量很小,可以直接二进制枚举每个点属于二分图的哪一部分,然后将两个端点分属两个部分的边加起来即可

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e6 + 5;
    int t, n, m,color[20],res=0,k;
    struct node{
        int u, v;
    } edge[200];
    int main(){
        cin>>t;
        while(t--){
            k++;
            res = 0;
            cin >> n >> m;
            for (int i = 0; i < m;i++){
                cin >> edge[i].u >> edge[i].v;
            }
            for (int i = 0; i < (1 << n);i++){
                for (int j = 0; j < n;j++){
                    if(i&(1<<j)){
                        color[j] = 1;
                    }
                    else{
                        color[j] = 0;
                    }
                }
                int sum = 0;
                for (int j = 0; j < m;j++){
                    int u=edge[j].u-1;
                    int v = edge[j].v-1;
                    if(color[u]!=color[v]){
                        sum++;
                    }
                }
                res = max(res, sum);
            }
            printf("Case #%d: %d
    ", k,res);
        }
        return 0;
    }
    

    L Light It Down

    M Blood Pressure Game

  • 相关阅读:
    Flex4中panel拖拽
    jquery两边飘浮的对联广告
    javascript 无刷新上传图片之原理
    第十二周--servlet做一个逻辑处理!!!!!!!!!!!!!实现登录
    第十一周--邮件系统补充一个注册一个登陆验证码
    第十周--邮件系统全套(第二版)
    第九周--邮件系统2(全套增删改查)
    第八周-邮件系统1
    第七周JSP增删改查
    JSP第六周 还是session
  • 原文地址:https://www.cnblogs.com/dyhaohaoxuexi/p/14045810.html
Copyright © 2011-2022 走看看