题意:
就是给你n个人,然后每个点有两个权值,然后有两个阵营,每个人可以进黎明阵营那么权值就是第一个,进黑暗阵营权值就是第二个。还有m个条件就是a和b不能在一个阵营。然后问你所有人分配好之后,最大的权值减去最小的权值最小为多少,如果非法输出impossible。
思考:
对于非法很明显就是有奇数环。然后就是分配阵营了,如何分配对吧,对于给的m条边,可以发现每个连通块的状态只有两种,一旦一个人的阵营确定的,剩下的人全部确定。对于每个连通块都有两种状态可言,对于状态我们只要最大值和最小值,因为只有最大值和最小值会影响最后的答案,所以把两个状态存下来。
然后就是怎么能让答案最小,可以先看这个简单问题:给你n种物品,每个物品有a,b两种权值,但是只能选其中一个,问你最后选完之后,最大的值减去最小的是多少。我们称a和b是孪生的,也就是不能同时选。其中这种答案变小的一般都是按值从小到大排序,然后当走到i点,如果当前的孪生没出现,放进去。如果出现了,那么让当前的值和出现过的值让最大的在里面,因为越往后权值越大,所以multiset里面的权值越大越好。如果种类达到n,那么就是multiset里面的最大值减去最小值。
所以这个题就是这个子问题再加上二分图,同时对于图里面的每个连通块的最大值和最小值都是可以用的。对于这个图的孪生是:颜色为1的去黎明还是颜色为1的去黑暗。同理这个也是这样,按最大值排序,如果出现了孪生,那么如果当前的最小值>孪生的最小值,那么肯定用当前的,把孪生删掉。到此这个题目就是这样了,很经典,感觉和之前做的cf的一次排序后再搞的差不多。
代码:
子问题:
int T,n,m,k;
PII va[N];
PII vb[N];
int vis1[N],vis2[N],vis[N];
int cnt,sum,minn = inf;
multiset<int > s;
signed main()
{
IOS;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>va[i].fi>>va[i].se;
vb[++cnt] = {va[i].fi,i}; //对孪生编号,是左边还是右边,便于下面的使用
vb[++cnt] = {va[i].se,i+n};
}
sort(vb+1,vb+1+cnt); //从小到大排序
for(int i=1;i<=cnt;i++)
{
int x = vb[i].fi,id = vb[i].se;
if(id<n)
{
if(vis[id]&&x>va[id].se) //如果孪生已经出现了并且用当前的更好
{
s.erase(s.find(va[id].se));
s.insert(x);
}
else //否则就直接放进去
{
sum++;
vis[id] = 1;
s.insert(x);
}
}
else
{
id -= n;
if(vis[id]&&x>va[id].fi)
{
s.erase(s.find(va[id].fi));
s.insert(x);
}
else
{
sum++;
vis[id] = 1;
s.insert(x);
}
}
if(sum==n) minn = min(minn,*s.rbegin()-*s.begin()); //更新答案
}
cout<<minn;
return 0;
}
本问题:
struct Node{
int minn,maxn;
int id;
}node[N];
int T,n,m,k;
PII va[N];
int col[N],vis[N];
int suc,cnt,sum;
vector<int > hav;
vector<int > e[N];
void init()
{
col[0] = 1;
cnt = sum = 0;suc = 1;
for(int i=1;i<=n;i++)
{
e[i].clear();
vis[i] = col[i] = 0;
}
}
void dfs(int now,int p)
{
hav.pb(now);
col[now] = 3-col[p];
for(auto spot:e[now])
{
if(spot==p) continue;
if(!col[spot]) dfs(spot,now);
else if(col[spot]==col[now]) suc = 0; //非法
}
}
bool cmp(Node A,Node B)
{
return A.maxn<B.maxn;
}
int solve()
{
multiset<int > s;
int anw = inf,res = 0;
sort(node+1,node+1+cnt,cmp); //按最大值从小到大排序,因为最小值肯定小于最大值,所以这样是可以保证答案更优的
for(int i=1;i<=cnt;i++)
{
int minn = node[i].minn,maxn = node[i].maxn,id = node[i].id;
if(!vis[id]) //如果孪生没出现
{
vis[id] = i;
s.insert(minn);s.insert(maxn);
if(++res==sum) anw = min(anw,*s.rbegin()-*s.begin()); //如果选够了sum个种类就更新答案
}
else //孪生的已经出现了
{
if(minn>node[vis[id]].minn) //如果用当前的更好
{
s.erase(s.find(node[vis[id]].minn));
s.erase(s.find(node[vis[id]].maxn));
s.insert(minn);s.insert(maxn);
}
if(res==sum) anw = min(anw,*s.rbegin()-*s.begin());
}
}
return anw;
}
signed main()
{
IOS;
cin>>T;
for(int cs=1;cs<=T;cs++)
{
cout<<"Case "<<cs<<": ";
cin>>n>>m;init();
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
e[a].pb(b);
e[b].pb(a);
}
for(int i=1;i<=n;i++) cin>>va[i].fi>>va[i].se;
for(int i=1;i<=n;i++)
{
if(!col[i])
{
hav.clear();sum++;
dfs(i,0);
for(int j=1;j<=2;j++)
{
int minn = inf,maxn = -inf; //对这个连通块的其中一种状态求出来最大值和最小值
for(auto t:hav)
{
if(col[t]==j)
{
minn = min(minn,va[t].fi);
maxn = max(maxn,va[t].fi);
}
else
{
minn = min(minn,va[t].se);
maxn = max(maxn,va[t].se);
}
}
node[++cnt] = {minn,maxn,sum}; //sum种类的两个孪生都放进去了
}
}
}
if(!suc) cout<<"IMPOSSIBLE\n";
else cout<<solve()<<"\n";
}
return 0;
}
总结:
多多思考,多多积累经验呀。