【题目记录】——ICPC南昌2019


题目集地址 ICPC2019南昌

B A Funny Bipartite Graph 状压DP

题目地址B A Funny Bipartite Graph
题目大意:一个二分图,左右各有n个点,左边第i个点有一个属性mi,它在一个图中的价值为midi,其中di为它在图中的度数(特殊的,如果度数为0,则价值为0),求一个该二分图的子图使得右边的每个点度数都不为0且总价值最小,输出最小价值。如果无解输出−1
有若干个限制条件(i,j)表示子图中左边的点i和j不能同时存在
保证:
原二分图中左边的每个点度数在[1,3]之间。
左边的i点和右边的j点连线当且仅当i ≤ j
n<=18
mi<=100
思路:参考文章
文章1
文章2
文章3
方法非常巧妙,我一开始想到状态压缩dp,那么把所有状态都存储下来,两边的点也都要存储很难优化。
看了他们的题解才知道根据题目的性质,说左侧的i选右侧的j,当且仅当i<=j,也就是说当我们考虑左侧的第i个点时,左侧的后n-i个还没选,右侧的前i个点必须全选(不然往后再也选不了),也就是说左侧的后n-i位和右侧的前i位都没啥用,所有我们可以将左侧的前i位和右侧的后n-i位拼成一起,这样 2 n 2^n 2n就可以存下,复杂度就是 O ( n ∗ 2 n ) O(n*2^n) O(n2n)
这波操作就相当于计组里面将32 位整数乘除法,它把乘数和结果同时存在了一个 64 位整数上
AC代码:

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

typedef long long LL;

const int maxn=20, maxN=262150;
const int inf=2139062143;

int n,N,AA[maxn],v[maxn][4],v0[maxn],M[maxn],f[maxn][maxN],er[maxn];
bool G[maxn][maxn],A[maxn][maxn];

void ReadBit(bool &data)
{
	char ch=getchar();
	while (ch!='0' && ch!='1') ch=getchar();
	data=(ch=='1');
}

inline void Min(int &a,const int &b) {a=(a<b) ?a :b ;}

int T;
int main()
{
	er[0]=1;
	fo(i,1,18) er[i]=er[i-1]<<1;
	
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		N=(1<<n)-1;
		fo(i,1,n)
		{
			v0[i]=0;
			fo(j,1,n)
			{
				ReadBit(G[i][j]);
				if (G[i][j]) v[i][++v0[i]]=j;
			}
		}
		fo(i,1,n)
		{
			AA[i]=0;
			fo(j,1,n)
			{
				ReadBit(A[i][j]);
				AA[i]|=A[i][j]<<(j-1);
			}
		}
		fo(i,1,n) scanf("%d",&M[i]);
		
		memset(f,127,sizeof(f));
		f[0][0]=0;
		fo(i,0,n-1)
			fo(s,0,N) if (f[i][s]<inf)
			{
				int nowL=s&(er[i]-1), nowR=s^nowL, nxt=i+1;
				bool alrdy=nowR&er[i];
				int cho=s|er[i];
				
				if (alrdy) Min(f[i+1][s^er[i]],f[i][s]);
				if (AA[nxt]&nowL || v0[nxt]==0) continue;
				
				if (alrdy || v[nxt][1]==nxt) Min(f[i+1][cho|er[v[nxt][1]-1]],f[i][s]+M[nxt]);
				if (v0[nxt]>1)
				{
					if (alrdy || v[nxt][2]==nxt) Min(f[i+1][cho|er[v[nxt][2]-1]],f[i][s]+M[nxt]);
					if (alrdy || v[nxt][1]==nxt || v[nxt][2]==nxt) Min(f[i+1][cho|er[v[nxt][1]-1]|er[v[nxt][2]-1]],f[i][s]+M[nxt]*M[nxt]);
				}
				if (v0[nxt]>2)
				{
					if (alrdy || v[nxt][3]==nxt) Min(f[i+1][cho|er[v[nxt][3]-1]],f[i][s]+M[nxt]);
					if (alrdy || v[nxt][1]==nxt || v[nxt][3]==nxt) Min(f[i+1][cho|er[v[nxt][1]-1]|er[v[nxt][3]-1]],f[i][s]+M[nxt]*M[nxt]);
					if (alrdy || v[nxt][2]==nxt || v[nxt][3]==nxt) Min(f[i+1][cho|er[v[nxt][2]-1]|er[v[nxt][3]-1]],f[i][s]+M[nxt]*M[nxt]);
					if (alrdy || v[nxt][1]==nxt || v[nxt][2]==nxt || v[nxt][3]==nxt) Min(f[i+1][cho|er[v[nxt][1]-1]|er[v[nxt][2]-1]|er[v[nxt][3]-1]],f[i][s]+M[nxt]*M[nxt]*M[nxt]);
				}
			}
		
		int ans=inf;
		fo(s,0,N) Min(ans,f[n][s]);
		printf("%d\n",(ans==inf) ?-1 :ans);
	}
}

E Bob’s Problem

题目地址E Bob’s Problem
题目大意:给出一个n nn个节点的无向图,有m mm条有权边,每条边非黑即白,现在要从中选择一些边构造新图,使得图连通,并且只能选择不超过k kk条白边,求出新图能获得的最大边权之和,如果无法使新图连通,输出-1
思路:按照先加黑边再从权值从大往小加白边的顺序向图中加边。维护一个最大生成树,黑边的权值全部加入到答案,白边最多加k kk条。然后判断是否已经连通,否则在判断白边是否加满k kk条,若为加满则把剩余白边从大到小补满到图中。

#include<bits/stdc++.h>
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

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

#include<bits/stdc++.h>
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 树上启发式合并

L Who is the Champion 签到

题目地址:L Who is the Champion
题目大意:有n支足球队参加比赛,每只队伍会和其余n−1队各比赛一场,现在给出所有比赛的结果,判断是否有冠军,如果有输出编号否则输出对应字符串,每场比赛胜者得3分,平局各得1分,冠军是分数最大且净进球数最多的队伍(净进球数=进球数-丢球数)
思路:按照题目模拟即可

#include<bits/stdc++.h>
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;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值