2019ICPC南昌题解

B-A Funny Bipartite Graph

题意:给出每一边的点数都不超过 n ≤ 18 n\leq18 n18的二分图,对左边的点标号 i i i 1 1 1 n n n,对右边的点标号 j j j 1 1 1 n n n,数据保证不会出现 i i i j j j的连边 ( i > j ) (i>j) (i>j),保证左边的每个点的度数都在 1 1 1 3 3 3之间。并且给出一个矩阵 A A A,在这个矩阵中,如果 A i , j = 1 A_{i,j}=1 Ai,j=1,那么左边的点 i i i和右边的点 j j j不可以同时出现,并且要求右边所有的点都有连边。求所有方案中 ∑ i = 1 n w i d e g i \sum_{i=1}^{n}w_{i}^{deg_{i}} i=1nwidegi的最小值。
做法:特判掉右边存在某个点无边可连的情形,直接输出 − 1 -1 1。否则由于 w i w_{i} wi为正,对于右边的点没有必要连超过 1 1 1条边,因此搜索的上界不超过 2 ∗ 18 ∗ 3 16 2*18*3^{16} 218316,且有不少的剪枝空间,可以通过。

#include
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
char str[20];
int g[20][20],A[20][20];
int w[20],tag[20],deg[20]; 
vector<int> go[20],con[20];
int ans=2e9;  
int cal(int x,int y) {
	if(!y) return 0; 
	int ans=1;
	for(int i=1;i<=y;++i) 
		ans=(ans*x);
	return ans; 
}
int n;  
void dfs(int cur,int A) {
	if(cur==n+1) { ans=min(ans,A);return; } 
	if(A>=ans) return;
	for(auto &v:go[cur]) {
		if(!tag[v]) {
			for(auto &x:con[v]) tag[x]++;
			deg[v]++;
			dfs(cur+1,A+cal(w[v],deg[v])-cal(w[v],deg[v]-1));
			deg[v]--;
			for(auto &x:con[v]) tag[x]--;
  		}
	}
}
void init() {
	memset(tag,0,sizeof(tag));
	memset(deg,0,sizeof(deg)); 
	for(int i=1;i<=n;i++) {
		go[i].clear();
		con[i].clear(); 
	}
}
int main() {
	int T;
	scanf("%d",&T);
	while(T--) {
		init(); 
		scanf("%d",&n);
		for(int i=1;i<=n;i++) {
			scanf("%s",str+1);
			for(int j=1;j<=n;j++)  {
				g[i][j]=str[j]-'0';
				if(g[i][j]) go[j].push_back(i); 	
			}	
		}
		for(int i=1;i<=n;i++) {
			scanf("%s",str+1);
			for(int j=1;j<=n;j++) {
				A[i][j]=str[j]-'0'; 
				if(A[i][j]) {
					con[i].push_back(j);
					con[j].push_back(i); 
				}
			}	
		}
		for(int i=1;i<=n;i++)
			scanf("%d",&w[i]); 
		bool ok=1; 
		for(int i=1;i<=n;i++) {
			if(!go[i].size()) {
				ok=0;
				break;
			}
		}
		if(!ok) puts("-1");
		else {
			ans=2e9; 
			dfs(1,0); 
			if(ans==2e9) puts("-1");
			else printf("%d\n",ans); 	
		}
	} 
	return 0;
}

C-And and Pair

题意:给出一个长度不超过 1 e 5 1e5 1e5的二进制01串,然后求问有多少个点对 ( i , j ) (i,j) (i,j)满足 0 ≤ j ≤ i ≤ n 0 \leq j \leq i \leq n 0jin并且 i & n = i i\&n=i i&n=i并且 i & j = 0 i\&j=0 i&j=0.
做法:显然 ( 0 , 0 ) (0,0) (0,0)是一个答案,否则就可以枚举 i i i的二进制的最高位的位置,统计除去最高位的低位 1 1 1的个数 x x x以及 0 0 0的个数 y y y
对于 0 0 0的位置, i i i当前位一定为 0 0 0 j j j当前一定有两种选择,所以为 2 y 2^{y} 2y
对于 1 1 1的位置,枚举 i i i 1 1 1的位置个数,如果 i i i 1 1 1显然 j j j只能填 0 0 0,而 i i i 0 0 0 j j j有两种选择,所以为 ∑ i = 0 x C x i 2 x − i = 3 x \sum_{i=0}^{x}C_{x}^{i}2^{x-i}=3^{x} i=0xCxi2xi=3x
每个最高位对答案的贡献是 3 x ∗ 2 y 3^{x}*2^{y} 3x2y

#include
typedef long long ll;
using namespace std;
const int mod=1e9+7;
const int N=1e5+7;
char s[N];
int fpow(ll x,int y) {
    ll ans=1;
    while(y) {
        if(y&1) ans=(ans*x)%mod;
        x=(x*x)%mod;
        y>>=1;
    }
    return ans;
}
int main() {
    int T;
    scanf("%d",&T);
    while(T--) {
        ll ans=0;
        scanf("%s",s+1);
        int n=strlen(s+1);
        int _1=0,_0=0;
        for(int i=n;i>=1;i--) {
            if(s[i]=='1') {
                ans+=1ll*fpow(3,_1)*fpow(2,_0)%mod;
                ans%=mod;
            }
            if(s[i]=='1') _1++;
            else _0++;
        }
        printf("%lld\n",(ans+1)%mod);
    }
    return 0;
}

E-Bob’s Problem

题意:给出一张 V ≤ 5 e 4 V\leq5e4 V5e4 E ≤ 5 e 5 E\leq5e5 E5e5的带权图,边分为黑边和白边,然后要求你选出一个边集,边集里面的白边的条数不超过 k k k,使得这个图是完全连通的,如果无法连通输出 − 1 -1 1,否则求出最大的权值和。
做法:按照先加黑边再从权值从大往小加白边的顺序向图中加边。维护一个最大生成树,黑边的权值全部加入到答案,白边最多加 k k k条。然后判断是否已经连通,否则在判断白边是否加满 k k k条,若为加满则把剩余白边从大到小补满到图中。

#include
using namespace std;
typedef long long ll;
const int M=1e6+5;
struct Edge{
    int u,v,w,c;
    bool operator <(const Edge &rhs) const {
        if(c<rhs.c) return 1;
        else if(c==rhs.c&&w>rhs.w) return 1;
        else return 0;
    }
}e[M];
int fa[M];
int fnd(int x) {
    if(fa[x]==x) return x;
    else return fa[x]=fnd(fa[x]);
}
bool cmp(int a,int b) {
    return a>b;
}
int main() {
    int T;
    scanf("%d",&T);
    while(T--) {
        int n,m,k;
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=n;i++) fa[i]=i;
        for(int i=1;i<=m;i++) {
            scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].c);
        }
        sort(e+1,e+1+m);
        ll ans=0;
        int cnt=0;
        vector<int> rest;
        rest.clear();
        for(int i=1;i<=m;i++) {
            if(e[i].c==0) {
                int u=e[i].u;
                int v=e[i].v;
                int U=fnd(u);
                int V=fnd(v);
                if(U==V) ans+=e[i].w;
                else fa[U]=V,ans+=e[i].w;
            }
            else if(e[i].c==1) {
                int u=e[i].u;
                int v=e[i].v;
                int U=fnd(u);
                int V=fnd(v);
                if(U==V) rest.push_back(e[i].w);
                else {
                    fa[U]=V;
                    ans+=e[i].w;
                    cnt++;
                    if(cnt==k) break;
                }
            }
        }
        set<int> s;
        s.clear();
        for(int i=1;i<=n;i++)
            s.insert(fnd(i));
        if(s.size()==1) {
            sort(rest.begin(),rest.end(),cmp);
            for(int i=0;i<min(k-cnt,(int)rest.size());i++) ans+=rest[i];
            printf("%lld\n",ans);
        }
        else {
            printf("%d\n",-1);
        }
    }
    return 0;
}

G-Eating Plan

题意:给出一个 n ≤ 1 e 5 n\leq1e5 n1e5的序列,序列的值是全排列的阶乘模上998857459,然后给出若干给查询,每次查询给出一个数 t t t,求最小的区间长度 x x x,使得某个区间 [ L , R ] [L,R] [L,R]满足 R − L + 1 = x R-L+1=x RL+1=x并且 ∑ i = 1 n a i ≥ x \sum_{i=1}^{n}a_{i}\geq x i=1naix
做法:注意到9988574592802的倍数,因此序列中实际有效的值只有2802个,通过2802*2802的预处理对所有的区间暴力按照区间和排序,同时维护区间长度后缀最小值,这样就可以做到二分做到单次 l o g log log的查询。

#include
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=998857459; 
struct Query {
	int val,id;
}a[3000]; 
int tot=0;
int fac[3000]; 
struct Two {
	int val,len;
	bool operator <(const Two &rhs) const {
		return val<rhs.val; 
	}
}b[2804*2804/2]; 
int mi[2804*2804/2];  
int p=0;
int main() {
	fac[0]=1;
	for(int i=1;i<=2802;i++)
		fac[i]=(1ll*fac[i-1]*i)%mod;
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		int x;
		scanf("%d",&x);
		if(x<=2802) {
			a[++tot].val=fac[x];
			a[tot].id=i;	
		}
	}
	for(int i=1;i<=tot;i++) {
		int sum=0;
		for(int j=i;j<=tot;j++) {
			sum=(sum+a[j].val)%mod;
			int len=a[j].id-a[i].id+1;
			b[++p].val=sum;
			b[p].len=len;
		}
	}
	sort(b+1,b+1+p);
	for(int i=p;i>=1;i--) {
		if(i==p) mi[i]=b[i].len;
		else mi[i]=min(b[i].len,mi[i+1]);
	}
	while(m--) {
		int x;
		scanf("%d",&x);
		int id=lower_bound(b+1,b+1+p,Two{x,0})-b;
		if(id==p+1) puts("-1");
		else printf("%d\n",mi[id]); 
	}
	return 0;
}

K-Tree

题意:给出一个 n ≤ 1 e 5 n\leq1e5 n1e5的树,求问这个树上有多少个点对 ( x , y ) (x,y) (x,y)满足:
1. 1. 1. 两个点 x , y x,y x,y互相不会成为彼此的祖先。
2. 2. 2. 两个点的权值 w x + w y = 2 ∗ w l c a ( x , y ) w_{x}+w_{y}=2*w_{lca(x,y)} wx+wy=2wlca(x,y) l c a ( x , y ) lca(x,y) lca(x,y) x x x y y y的最近公共祖先。
3. 3. 3. 两个点的距离不会超过 k k k
做法:考虑启发式合并,先对树重链剖分,然后对于每个子树 i i i的答案,把 i i i作为 l c a lca lca统计答案,先计算轻边但不保留信息,再计算重边且保留信息,最后再去计算轻边统计答案。用平衡树维护每个权值对于所有点的深度,暴力统计。

#include
#include
typedef long long ll;
using namespace std;
using namespace __gnu_pbds;
typedef pair<int,int> pii;
const int N=2e5+7;
int n,k;
ll ans=0;
vector<int> go[N];
int w[N];
int sz[N],d[N],son[N];
tree<pii, null_type, less<pii>, rb_tree_tag, tree_order_statistics_node_update> t[N];
void dfs(int u) {
    sz[u]=1;
    for(auto &v:go[u]) {
        d[v]=d[u]+1;
        dfs(v);
        sz[u]+=sz[v];
        if(sz[v]>sz[son[u]])
            son[u]=v;
    }
}
void merge(int u,int lca) {
    int x=2*w[lca]-w[u];
    if(x>=0) ans+=t[x].order_of_key(pii(2*d[lca]-d[u]+k,2e9));
    for(auto &v:go[u]) merge(v,lca);
}
void update(int u,int lca,int opt) {
    if(opt==1) t[w[u]].insert(pii(d[u],u));
    else if(opt==-1) t[w[u]].erase(pii(d[u],u));
    for(auto &v:go[u]) update(v,lca,opt);
}
void dsu(int u,bool ok) {
    for(auto &v:go[u]) {
        if(v==son[u]) continue;
        dsu(v,0);
    }
    if(son[u]) dsu(son[u],1);
    for(auto &v:go[u]) {
        if(v==son[u]) continue;
        merge(v,u);
        update(v,u,1);
    }
    t[w[u]].insert(pii(d[u],u));
    if(!ok) update(u,u,-1);
}
int main() {
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    for(int i=2;i<=n;i++) {
        int fa;
        scanf("%d",&fa);
        go[fa].push_back(i);
    }
    dfs(1);
    dsu(1,1);
    printf("%lld\n",2*ans);
    return 0;
}

L-Who is the Champion

题意:签到题,给出足球赛事的规则。
做法:按照题意模拟。

#include
using namespace std;
const int N=105;
int mp[N][N];
struct Player {
    int grade,score,id;
    bool operator <(const Player &rhs) const {
        if(grade>rhs.grade) return 1;
        else if(grade==rhs.grade&&score>rhs.score) return 1;
        else return 0;
    }
}a[N];
int main() {
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) {
            scanf("%d",&mp[i][j]);
        }
    }
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) {
            if(j==i) continue;
            if(mp[i][j]>mp[j][i]) a[i].grade+=3;
            else if(mp[i][j]==mp[j][i]) a[i].grade+=1;
            a[i].score+=mp[i][j]-mp[j][i];
        }
        a[i].id=i;
    }
    sort(a+1,a+1+n);
    if(n==1) printf("%d\n",a[1].id);
    else {
        if(a[1].grade==a[2].grade&&a[1].score==a[2].score)
            puts("play-offs");
        else printf("%d\n",a[1].id);
    }
    return 0;
}

你可能感兴趣的:(比赛题解)