Topcoder SRM617-Div1-Lv2 PieOrDolphin
涉及知识点:贪心,图论转化建图
题意
有 \(n\ (\leq50)\) 个人,给他们发礼物,共有 \(m\ (\leq1000)\) 天,每天要给两个人发礼物,其中一个人获得一号礼物,另一个获得二号礼物,定义一个方案的总和为每个人获得的一号二号礼物数之差的和。现在每一天要发礼物的两个人已经确定,但是你可以选择给谁发一号或二号礼物,构造方案使得总和最小。
思路
将每一天的礼物方案抽象为一条边,获得一号礼物的人向获得二号礼物的人连一条有向边,因此每个人的出度即为获得一号礼物数,入度同理为二号礼物数,那么我们的目标即为通过确定每一条边的方向来最小化每个点入度出度之差。直接遍历并直接确定方向十分困难,我们考虑不断贪心反转边来寻找更优的答案,具体来说,考虑到对于一条简单路径,如果反转路径上所有边,那么路径上除了首尾端点的其他点不会受到影响(原先的一个出度变为入度,原先的一个入度变为出度),而首端点则出度 \(-1\),入度 \(+1\),尾端点反之。因此,我们每次找到出入度之差 \(\geq2\) 的点 \(st\),以 \(outd[i]-ind[i]\geq 2\) 的情况为例,我们再找到另外一个 \(ind[i]-outd[i]\geq 1\)[1] 的点 \(ed\),将 \(st\) 到 \(ed\) 简单路径的全部边反转即可,为了方便处理,对于 \(ind[i]-outd[i]\geq 2\) 的情况,直接将整张图反转,再用相同方法处理。
代码
#include<bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar __getchar
inline char __getchar(){
static char ch[1<<20],*l,*r;
return (l==r&&(r=(l=ch)+fread(ch,1,1<<20,stdin),l==r))?EOF:*l++;
}
#endif
template<class T>inline void rd(T &x){
T res=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
while('0'<=ch && ch<='9'){res=res*10+ch-'0';ch=getchar();}
x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
static char wtbuff[20];
static int wtptr;
if(x==0){
putchar('0');
}
else{
if(x<0){x=-x;putchar('-');}
wtptr=0;
while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
while(wtptr--) putchar(wtbuff[wtptr]);
}
if(endch!='\0') putchar(endch);
}
const int MAXN=1e3+5,MAXM=55;
typedef pair<int,int> pii;
int n,ans[MAXN],ind[MAXM],oud[MAXM];//ans[i]=1正向,2逆向
pii gift[MAXN];
inline void solve(){
int st,ed,cur;
pii pre[MAXM];
while(1){
st=-1;ed=-1;
for(int i=0;i<50;i++){
if(abs(ind[i]-oud[i])>=2){
st=i;break;
}
}
if(st==-1) return;
if (ind[st]>oud[st]){
for(int i=1;i<=n;i++) ans[i]=3-ans[i];
for (int i=0;i<50;i++) swap(ind[i],oud[i]);
}
fill(pre,pre+50,make_pair(-1,-1));
queue<int>q;q.push(st);
while(!q.empty()){
cur=q.front();q.pop();
if(ind[cur]>oud[cur]){
ed=cur;break;
}
for(int i=1,u,v;i<=n;i++){
u=gift[i].first;v=gift[i].second;
if(ans[i]==2) swap(u,v);
if(u==cur && v!=st){
if(pre[v].first==-1){
pre[v]=make_pair(u,i);
q.push(v);
}
}
}
}
cur=ed;
int nxt;
while(pre[cur].first!=-1){
nxt=pre[cur].first;
ans[pre[cur].second]=3-ans[pre[cur].second];
ind[cur]--;
oud[cur]++;
ind[nxt]++;
oud[nxt]--;
cur=nxt;
}
}
}
int main(){
rd(n);
for(int i=1;i<=n;i++){
rd(gift[i].first);
oud[gift[i].first]++;
}
rd(n);
for(int i=1;i<=n;i++){
rd(gift[i].second);
ind[gift[i].second]++;
}
fill(ans+1,ans+1+n,1);
solve();
for(int i=1;i<=n;i++){
wt(ans[i],' ');
}
// int cnt=0;
// for(int i=0;i<50;i++){
// cnt+=abs(ind[i]-oud[i]);
// }
// cout<<cnt<<endl;
return 0;
}
为什么尾端点只需要 \(\geq 1\) 即可?因为只要尾端点有差值,反转后最坏情况尾端点无贡献,但是首端点一定有贡献,这样做才能找到所有可能的贡献 ↩︎