文章目录
题目集地址 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(n∗2n)
这波操作就相当于计组里面将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
n≤1e5的序列,序列的值是全排列的阶乘模上998857459,然后给出若干给查询,每次查询给出一个数t,求最小的区间长度x,使得某个区间[L,R]满足R-L+1=x并且
∑
i
=
1
n
a
i
≥
x
\sum_{i=1}^{n}a_{i}\geq x
∑i=1nai≥x
思路: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;
}