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\n", k);
        }
        else{
        printf("Case #%d: No\n", k);
        }
    }
    return 0;
}

C Maze

D Spanning Tree Removal

大意:

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

思路:

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

2019 ICPC Shanghai Regional Contest

就是将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\n", 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\n", 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:\n", 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\n", 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\n", 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\n", k,res);
    }
    return 0;
}

L Light It Down

M Blood Pressure Game

上一篇:docker启动jar应用容器时间出错


下一篇:tars框架安装