0
点赞
收藏
分享

微信扫一扫

2021 acm-icpc区域赛(上海)补题笔记

乌龙茶3297 2022-03-31 阅读 59

前言

赛前打几场重现赛模拟一下,争取把力所能及的题都补了

yysy,今年大部分赛站卷的程度已经非往年可比的了…

比赛链接:https://ac.nowcoder.com/acm/contest/24872

文章目录

题目一览

签到题:D,E,G,I

铜牌题:H

银牌题:J,K,M

金牌题:B

大概不是我能做的题:A,C,F,L

本场差不多是5题铜,6.5题银,8题金

D.Strange_Fractions(签到)

题意

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iI3u9PE6-1648702495288)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220331112836696.png)]

思路

初中数学,设 x = a b x = \frac ab x=ba , 有 p q = x + 1 x \frac pq = x + \frac1x qp=x+x1 , 求根公式解出来即可。

E.Strange_Integerss(签到)

题意

n 个数中选出 m 个数使得两两之差绝对值不低于 k , 要求最⼤化 m

11 2
3 1 4 1 5 9 2 6 5 3 5
4

思路

排序后从⼩到⼤贪⼼选取合法且尽可能接近的数字即可

G.Edge Groups(树形dp)

题意

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RQZdq7iT-1648702495288)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220331113937997.png)]

求树分解成若⼲⻓度为 的路径的⽅案数。

7
1 2
1 3
1 7
4 7
5 7
6 7
3

思路

很板子的树dp,队友写的,没看。官方题解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L9dqzk1P-1648702742213)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220331114232880.png)]

I.Steadily Growing Steam(背包)

题意:

题目有点长,大意是:

n件物品具有体积 t i t_i ti 和价值 v i v_i vi ,选出⾄多 k k k 件物品 将其体积翻倍,然后选出若⼲物品并将其分为 体积和 相同的两堆 S , T S,T S,T,问选出的物品 价值之和 最⼤是多少。

输入

4 1
10 1
-5 3
5 1
6 1

输出

21

One possible scheme:

Double t 1 t_1 t1 and choose that S = { 1 } , T = { 3 , 4 } S=\{1\},T=\{3,4\} S={1},T={3,4}, where the point number sum are both 2, and the sum of the card values is 10 + 5 + 6 = 21 10+5+6=21 10+5+6=21.

思路:

很显然是01背包,其实挺签到的,但却把我们卡了一个多钟。

主要是一直在想两个集合怎么相互转移的问题。

后来想到两个集合是可以合并的。

我们假设装进集合S的物品体积为 + t i +t_i +ti , 那么可以假设装进集合T的物品体积为 − t i -t_i ti ,这样动态转移的终点就会在体积和 V = 0 V=0 V=0 处了。

具体地,设 d p [ N ] [ V ] [ K ] dp[N][V][K] dp[N][V][K] 表示当前在第i个物品 , 体积和为V,已经将K件物品翻倍。

然后第 i i i个物品只会从第 i − 1 i-1 i1 个物品转移,所以第一维的N可以用滚动数组滚掉,变成 d p [ 2 ] [ V ] [ K ] dp[2][V][K] dp[2][V][K].

我们将每个物品拆分成四个:

a a = ( v i , t i ) aa = (v_i , t_i) aa=(vi,ti) , b b = ( v i , − t i ) bb = (v_i ,- t_i) bb=(vi,ti)

c c = ( v i , 2 ∗ t i ) , cc = (v_i , 2*t_i) , cc=(vi,2ti), d d = ( v i , − 2 ∗ t i ) dd = (v_i , -2*t_i) dd=(vi,2ti)

那么:

d p [ i ] [ V ] [ K ] = m a x ( d p [ i ] [ V ] [ K ] , d p [ i ⊕ 1 ] [ V − a a ] [ j ] + v [ i ] ) dp[i][V][K] = max(dp[i][V][K],dp[i\oplus1][V-aa][j]+v[i]) dp[i][V][K]=max(dp[i][V][K],dp[i1][Vaa][j]+v[i])

d p [ i ] [ V ] [ K ] = m a x ( d p [ i ] [ V ] [ K ] , d p [ i ⊕ 1 ] [ V − b b ] [ j ] + v [ i ] ) dp[i][V][K] = max(dp[i][V][K],dp[i\oplus1][V-bb][j]+v[i]) dp[i][V][K]=max(dp[i][V][K],dp[i1][Vbb][j]+v[i])

d p [ i ] [ V ] [ K ] = m a x ( d p [ i ] [ V ] [ K ] , d p [ i ⊕ 1 ] [ V − c c ] [ j − 1 ] + v [ i ] ) dp[i][V][K] = max(dp[i][V][K],dp[i\oplus1][V-cc][j-1]+v[i]) dp[i][V][K]=max(dp[i][V][K],dp[i1][Vcc][j1]+v[i]) , K > 0 , K>0 ,K>0

d p [ i ] [ V ] [ K ] = m a x ( d p [ i ] [ V ] [ K ] , d p [ i ⊕ 1 ] [ V − d d ] [ j − 1 ] + v [ i ] ) dp[i][V][K] = max(dp[i][V][K],dp[i\oplus1][V-dd][j-1]+v[i]) dp[i][V][K]=max(dp[i][V][K],dp[i1][Vdd][j1]+v[i]) , K > 0 ,K>0 ,K>0

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 111;
int dp[2][5555][111];
int n,k;
int v[N],t[N];
signed main(){
    ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>v[i]>>t[i];
    }
    for(int i=0;i<=5500;i++)
        for(int j=0;j<=110;j++)
            dp[0][i][j] = dp[1][i][j] = -1e15;
        int now = 0,ans = 0;
        dp[now][2800][0] = 0;
        for(int i=1;i<=n;i++){
            now = now^1;
            int aa = t[i],bb = -t[i],cc = 2*t[i],dd = -2*t[i];
            for(int V=-2600;V<=2600;V++){
                for(int j=0;j<=k;j++){
                    int tmp = V+2800;
                    dp[now][tmp][j] = dp[now^1][tmp][j];
                    dp[now][tmp][j] = max(dp[now][tmp][j],dp[now^1][tmp-aa][j]+v[i]);
                    dp[now][tmp][j] = max(dp[now][tmp][j],dp[now^1][tmp-bb][j]+v[i]);
                    if(k>0){
                        dp[now][tmp][j] = max(dp[now][tmp][j],dp[now^1][tmp-cc][j-1]+v[i]);
                        dp[now][tmp][j] = max(dp[now][tmp][j],dp[now^1][tmp-dd][j-1]+v[i]);
                    }
                    ans = max(ans,dp[now][tmp][j]);
                }
            }
        }
        cout<<ans<<endl;
}

H.Life is a Game (kruskal重构树+树上倍增)

题意:

⼀张带边权带点权⽆向图。从某点出发,有初始声望。 每第⼀次到达⼀个点将获得点权等值的声望加成。

经过⼀条边需要满⾜边权等值的最低声望限制。 多次给出起点和初始声望,询问能达到的最⼤声望。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jKwrc5XZ-1648702495289)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220331120606044.png)]

思路:

铜牌题越来越难了啊。

我们不会kruskal重构树,那天用堆+启发式合并硬搞出来的。

现在补题主要写一写kruskal重构树的解法,毕竟可以离线做这道题。

洛谷上的kruskal重构树:https://www.luogu.com.cn/problem/P7834 (不过洛谷这题加了主席树维护第k大)

其实就是在kruskal的过程中建树:

把边按边权从小到大排序,并查集合并两端点 u , v u,v u,v 的同时新建一个节点 t o t tot tot , 节点 t o t tot tot连接 u , v u,v u,v , 且维护 u , v u,v u,v点的共同信息

在本题中 , t o t tot tot 节点可以维护两个信息 , a u + a v a_u + a_v au+av w ( u , v ) w(u,v) w(u,v)

本题的感想是kruskal重构树是一个很好的思路,它用很少的时间和空间维护了并查集的一些关键信息。

样例的重构树长这样:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dJg7SGYt-1648702495289)(C:\Users\xi2001\AppData\Roaming\Typora\typora-user-images\image-20220331122050153.png)]

然后树上倍增维护每个节点的第i级父节点,查询的时候倍增地查就好了。

官方解答:

在这里插入图片描述

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+100;
struct node{
    int u;
    int v;
    int w;
    bool operator<(node B) const{
        return w<B.w;
    }
}e[N<<1];
int a[N<<1];
int n,m,q;
int fa[N<<1];
vector<int> g[N<<1];
int val[N<<1];
int ff[N<<1][22];
int tot;
int find_fa(int x){
    return (fa[x]==x)?fa[x]:fa[x] = find_fa(fa[x]);
}
void Kruskal(){
    tot = n;
    for(int i=1;i<=m;i++){
        int u = find_fa(e[i].u),v = find_fa(e[i].v),w = e[i].w;
        if(find_fa(u)!= find_fa(v)){
            tot++;
            val[tot] = w;
            g[tot].push_back(u);
            g[tot].push_back(v);
            g[u].push_back(tot);
            g[v].push_back(tot);
            fa[u] = fa[v] = fa[tot] = tot;
        }
    }
}
void dfs(int u,int f){
    ff[u][0] = f;
    for(int i=1;i<=20;i++){
        ff[u][i] = ff[ff[u][i-1]][i-1];
    }
    for(auto v:g[u]){
        if(v==f) continue;
        dfs(v,u);
        a[u] += a[v];
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++) cin>>a[i],fa[i] = i;
    for(int i=1;i<=m;i++){
        int x,y,z;
        cin>>x>>y>>z;
        e[i] = {x,y,z};
    }
    sort(e+1,e+1+m);
    Kruskal();
    dfs(tot,0);
    val[0] = 1e15+7;
    while(q--){
        int u,x;
        cin>>u>>x;
        int now = x+a[u];
        while(now!=tot){
            //cout<<u<<" "<<x<<endl;
            bool ok = false;
            for(int i=20;i>=0;i--){
                if(val[ff[u][i]]<=now){
                    u = ff[u][i];
                    ok = true;
                }
            }
            if(!ok) break;
            now = x+a[u];
        }
        cout<<now<<endl;
    }
    return 0;
}

K.Circle of Life(打表+构造)

题意

在这里插入图片描述

思路

找规律题,感觉如果把重构树写完还能剩一些时间的话大概率都能写写这题。。。

把题意模拟出来,然后发现是构造

发现n = 6时只有两种解:100110,(另一个忘了)

然后以这两个为主去找规律,发现1001可以作为循环节,然后没了。

代码:

#include<bits/stdc++.h>
using namespace std;
string s[4] = {"1001","10001","100110","1001010"};
int main(){
    ios::sync_with_stdio(false);
    int n;
    cin>>n;
    if(n==2){
        cout<<"10"<<endl;
    }
    else if(n==3){
        cout<<"Unlucky"<<endl;
    }
    else if(n<=7){
        cout<<s[n-4]<<endl;
    }
    else{
        int tot = (n-4)/4;
        int res = (n-4)%4;
        for(int i=0;i<tot;i++) cout<<"1001";
        cout<<s[res]<<endl;
    }
}

J.Two Binary Strings Problem(bitset+位运算)

题意:

在这里插入图片描述

在这里插入图片描述

输入

2
5
11010
11000
8
11110000
11111100

输出

01000
00001100

思路:

会不会用bitset决定了这题能不能写。。。。

很显然,打暴力的话复杂度是 O ( n 2 ) O(n^2) On2)

对于32位整型INT , 用bitset通过位运算打暴力的复杂度是 O ( n 2 32 ) O(\frac {n^2}{32}) O(32n2)

但是细节很多,对着逆十字的代码看了半天才弄明白。。。

把0变成-1,然后维护前缀和 s u m [ ] sum[] sum[]

把前缀和排个序,大的在前面

然后按顺序遍历一遍

于是惊奇的发现,如果前面访问的位置 i i i比之后访问的位置 j j j小,那么 j j j这个位置肯定是不行的

因为既然有 s u m [ i ] > s u m [ j ] sum[i] > sum[j] sum[i]>sum[j] i < j i < j i<j

那么就必然存在一个 k k k ,使得 s u m [ j ] − s u m [ j − k ] < = 0 sum[j] - sum[j-k] <=0 sum[j]sum[jk]<=0 ,也就是 [ j − k , j ] [j-k,j] [jk,j] 这个区间的0不比1少

所以开一个bitset A , 把顺序遍历时对应的位置pos标上,代表该位置被访问了。

对于 b [ i ] = 0 b[i] = 0 b[i]=0 的情况,其实就是将 b [ i ] = 1 b[i] = 1 b[i]=1时的各项取反

那么再开一个bitset one,置为全1 , 因为二进制数 异或 全1就是取反。

然后一个bitset ans 记录答案,每次遍历时拿bitset A 更新ans.

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 50500;
char a[N],b[N];
int s[N],id[N];
bitset<N> A,ans,one;
bool cmp(int xx,int yy){
    return s[xx]==s[yy]?xx<yy:s[xx]>s[yy];
}
int main(){
    ios::sync_with_stdio(false);
    int t;
    cin>>t;

    while(t--){
        A.reset(),ans.reset(),one.reset();
        int n;
        cin>>n;
        cin>>(a+1);
        cin>>(b+1);
        int tg = n+1;
        for(int i=1;i<=n;i++) one[i] = 1;
        id[0] = 0;
        for(int i=1;i<=n;i++){
            s[i] = s[i-1]+((a[i]=='1')?1:-1);
            id[i] = i;
        }
        sort(id,id+1+n,cmp);
        for(int i=0;i<=n;i++){
            int pos = id[i];
            if(pos) {
                if (b[pos] == '1') {
                    ans = ans | (A >> (n - pos));
                    if (s[pos] <= 0) tg = min(tg, pos + 1);
                } else {
                    ans = ans | ((A ^ one) >> (n - pos));
                    if (s[pos] > 0) tg = min(tg, pos + 1);
                }
            }

            A[n-pos] = 1;
        }
        for(int i=1;i<=n;i++){
            if(ans[i]||i>=tg) cout<<0;
            else cout<<1;
        }
        cout<<endl;
    }
}

B.Strange Permutations (生成函数 / NTT+容斥)

呜呜呜,不会生成函数,不会快速傅里叶变换,不会数论变换

呜呜呜,我怎么什么都不会呀

有空再更吧。。。。

举报

相关推荐

0 条评论