School of Software Engineering, SCUT, 2021记录(2020 ICPC)

cf链接:Dashboard - The 2020 ICPC Asia Taipei-Hsinchu Site Programming Contest - Codeforces

B. Make Numbers(dfs+模拟,优先级问题)

思路:考虑把连接也看成一种运算,那么就有+、-、*、连接 这四种运算,优先级是:

                                                   【连接】 > 【*】 > 【+、-】

注意数字的顺序会影响运算结果,所以考虑用全排列+dfs,其中dfs负责符号运算。

注意dfs中的去重操作:相同vector,相同的当前操作,对答案贡献就重复了,要用set去重。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<endl;
// #define int long long
#define pii pair<int,int>
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
const int N = 1e4+5;
set<pair<vector<int>,int>> st;
int ans=0,vis[N];

void dfs(vector<int> v,int op){
    if(st.count({v,op})) return;
    st.insert({v,op});
    int sz=v.size();
    if(sz==1){ //只剩一个数,可以检查是否满足题意
        if(v[0]>=0 && !vis[v[0]]) ans++, vis[v[0]]=1; //没有重复,符合题意
        return;
    }
    if(op==1 || op==2){ //处理连接和*
        dfs(v,op+1); //跳过这一步
        if(sz==2 && op==1) return; //全部连一起,非法了
        FOR(i,0,sz-2){ //遍历op的作用位置
            vector<int> t; //临时vector,用来存处理完之后的结果
            FOR(j,0,i-1) t.push_back(v[j]); //把前面的数放进去
            if(op==1) t.push_back(v[i+1]>=10 ? v[i]*100+v[i+1] : v[i]*10+v[i+1]); //1.合并
            if(op==2) t.push_back(v[i]*v[i+1]); //2.乘法
            FOR(j,i+2,sz-1) t.push_back(v[j]); //把后面的数放进去
            dfs(t,op); //重复本操作
        }
    }
    if(op==3){ //处理+和-
        vector<int> t;
        t.push_back(v[0]+v[1]); //加法
        FOR(j,2,sz-1) t.push_back(v[j]); //把后面的数放进去
        dfs(t,op);
        t[0] = v[0]-v[1]; //换成-
        dfs(t,op);
    }
}
signed main(){
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    vector<int> v;
    int x; FOR(i,1,4) cin>>x, v.push_back(x);
    sort(v.begin(), v.end());
    do{dfs(v,1);}while(next_permutation(v.begin(), v.end()));
    cout<<ans<<'\n';
} 

C. Pyramid (思维,简单dp)

注意到一个非常重要的性质,一个点走过x次,那么它的左孩子走过(x+1)/2次(即一半向上取整),右孩子走过x/2次(即一半向下取整)。这样就可以dp推出前k-1次每个点走过的小球个数。写法很简单。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<endl;
#define pii pair<int,int>
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
const int inf = 0x3f3f3f3f;
const int N = 1e4+5;
int T,n,k, f[N][N];

inline void solve(){
	FOR(i,0,n-1) FOR(j,0,i) f[i][j]=0; //init
	cin>>n>>k;
	int ans=0; //ans表示当前走到的位置(每往右走一次,ans就+1)
	f[0][0]=k-1;
	for(int i=0; i<n-1; i++){
		if(f[i][ans]%2) ans++;
		for(int j=0; j<=i; j++){
			f[i+1][j] += (f[i][j]+1)/2;
			f[i+1][j+1] += f[i][j]/2;
		}
	}
	cout<<ans<<'\n';
}
signed main(){
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin>>T; while(T--) solve();
}

E. A Color Game(区间dp)

[i,j]区间中,如果可以只剩余k类字母的话,那么用f[i][j][k]来记录最多剩余k类字母的数量。

如果不能只剩余k类字母(即至少要剩余两种字母),那么f[i][j][k]用-inf作为非法标记

如果能全部消除,但一个k类字母也剩余不了,那f[i][j][k]就是0(剩了0个也算它剩了,因为它没有不合法)

dp初始化是f[i][i][mp[s[i]]=1,其他都用-inf标记。

剩下就是基础的区间dp了。注意如果一个[l,r]区间可以被全部消除,需要标记所有f[l][r][k]为合法(至少也得是0,可以更大)

最终,f[0][n-1][k]必须要有一个>=m,来完成最后的一次消除。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<endl;
#define pii pair<int,int>
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
const int N = 550;
map<char,int> mp;
string s;
int m, f[N][N][10];

inline void getmx(int&a,int b){a=max(a,b);}
inline void init(){
    memset(f,-0x3f3f3f3f,sizeof(f));
    mp['R'] = 0; mp['G'] = 1; mp['B'] = 2;
    mp['C'] = 3; mp['M'] = 4; mp['Y'] = 5;
    mp['K'] = 6;
}
signed main(){
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    init();
    cin>>s>>m; int n=s.size();
    if(m==1) {cout<<"Yes"<<'\n'; return 0;} //特判m=1
    FOR(i,0,n-1) f[i][i][mp[s[i]]]=1;
    FOR(len,2,n) for(int l=0,r=l+len-1; r<=n; l++,r++){
        bool ok=0;
        for(int j=l; j<r; j++){ //断点
            FOR(c,0,6){
                getmx(f[l][r][c], f[l][j][c]+f[j+1][r][c]); //[l,r]区间中最多剩多少个c?
                if(f[l][r][c]>=m) ok=1; //[l,r]区间合法了
            }
        }
        if(ok) FOR(c,0,6) getmx(f[l][r][c],0); //合法化设置
    }
    FOR(i,0,n-1) if(f[0][n-1][i]>=m) {cout<<"Yes"; return 0;}
    cout<<"No";
} 

F. Cable Protection 

题意:求基环树的最小点覆盖。(基环树就是中间有一个环的树,把环中一条边切掉就是普通树)

思路:基环树不好弄,考虑直接切掉一条边变成普通树。比如切s-t,然后因为s,t中必须至少有一个要放,所以s和t各dfs一次,答案就是min(s放,t放)。注意因为分别让s放了,t放了,所以切掉的边不会影响结果。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<endl;
#define pii pair<int,int>
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
const int N = 2e5+5,inf=0x3f3f3f3f;
vector<int> g[N];
int n,m,s=-1,t=-1;
int f[N][2];

void dfs(int u,int fa){
    f[u][0]=1; f[u][1]=0;
    for(int v:g[u]){
        if(v==fa) continue;
        dfs(v,u);
        f[u][0]+=min(f[v][0],f[v][1]);
        f[u][1]+=f[v][0];
    }
}
signed main(){
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin>>n>>m; int u,v;
    FOR(i,1,n+m){
        cin>>u>>v;
        if(u<n && v<n && s==-1){ //本题核心!!找出环中要切掉的一条边
            s=u; t=v; continue;
        }
        g[u].push_back(v); g[v].push_back(u);
    }
    dfs(s,-1);
    int ans1=f[s][0];
    memset(f,0,sizeof(f));
    dfs(t,-1);
    cout<<min(ans1,f[t][0]);
}

H. Optimization for UltraNet

题意:给出一个图,给出的边可用可不用,求一棵生成树,使得:1.首先保证树的最小边权最大。2.在保证1的前提下,使得所有边权的和最小。

题目要求输出所有点对的路径上最小边权的和。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<endl;
#define pii pair<int,int>
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
const int inf=0x3f3f3f3f;
const int N = 1e4+5, M=5e5+5;
int n,m,fa[N],f[N];
long long ans=0;
struct node{int to,w;}; vector<node> g[N];
struct edge{int u,v,w;} e[M];

inline bool cmp(const edge&a,const edge&b){return a.w<b.w;} //按权值排序
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} //并查集find函数
inline void init(){FOR(i,1,n) fa[i]=i;} //init并查集
inline int check(int x){ //检查最小权值边是否能>=x
    init(); int cnt=n;
    FOR(i,1,m){
        if(cnt==1) break; //已经连通,ok
        if(e[i].w < x) continue; //跳过权值不够的
        int r1=find(e[i].u), r2=find(e[i].v);
        if(r1!=r2) cnt--, fa[r2]=r1; //merge r1 with r2
    }
    if(cnt==1) return 1; //连通,ok
    else return 0; //不连通,false
}
inline void build(int x){ //根据二分得到的最小边权结果,建一棵树
    //要注意,相同最小边权可能建出不同的数,如果把合法边全加上了,可能是一个图而不是树
    //所以还要根据连通性来保证建的是一棵树而不是一个图(否则后面dfs会出问题)
    init(); int cnt=0;
    FOR(i,1,m){ //按照边权从小到大建树,这样就能保证总边权最小!!
        if(cnt==n-1) return; //建树完成,结束
        if(e[i].w < x) continue; //权值不够,跳过
        int r1=find(e[i].u), r2=find(e[i].v);
        if(r1==r2) continue; //已经连通,跳过

        //下面进行建边
        int u=e[i].u, v=e[i].v, w=e[i].w;
        g[u].push_back({v,w});
        g[v].push_back({u,w});
        fa[r2] = r1; //建完边之后,记得记录连通,防止边建多了
        cnt++; //建边数量+1
    }
}
void dfs(int u,int fa){ //dfs求出起点s到每个点的最小边权
    for(auto i:g[u]){
        int v=i.to, w=i.w;
        if(v==fa) continue;
        f[v]=min(f[u],w);
        ans+=f[v];
        dfs(v,u);
    }
}
signed main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin>>n>>m;
    FOR(i,1,m) cin>>e[i].u>>e[i].v>>e[i].w;
    sort(e+1,e+m+1,cmp); //按权值排序
    int l=0, r=1e7, mid, res;
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid)) res=mid, l=mid+1;
        else r=mid-1;
    }
    build(res);
    FOR(i,1,n){
        f[i]=inf;
        dfs(i,0);
    }
    cout<<ans/2;
}

M. Keystroke 

暴力枚举所有情况,因为总共就16种情况,预处理一下即可。

#include<bits/stdc++.h>
using namespace std;
#define debug cout<<"!!!"<<endl;
#define pii pair<int,int>
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
const int inf = 0x3f3f3f3f;
const int N = 2e5+5;
set<int> zuo[20], you[20];
int T,n,m,a[N],b[N];

inline void init(){
    FOR(i,0,15){
        for(int j=0; j<4; j++){
            if(!((i>>j)&1)) continue;
            if(j==0) zuo[i].insert(0), you[i].insert(0);
            else if(j==1) zuo[i].insert(0), you[i].insert(1);
            else if(j==2) zuo[i].insert(1), you[i].insert(0);
            else zuo[i].insert(1), you[i].insert(1);
        }
	}
}
inline void solve(){
    cin>>n>>m;
    FOR(i,1,n) cin>>a[i];
    FOR(i,1,m) cin>>b[i];
	int ans=0;
	FOR(i,0,15){
		if(!(n==zuo[i].size() && m==you[i].size())) continue;
		bool ok=1;
		FOR(j,1,n) if(!zuo[i].count(a[j])) {ok=0; break;}
		FOR(j,1,m) if(!you[i].count(b[j])) {ok=0; break;}
		if(ok)ans++;
	}
	cout<<ans<<'\n';
}
signed main(){
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    init();
    cin>>T; while(T--) solve();
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值