题解:UVA10276 Hanoi Tower Troubles Again!
1.28 修改了一些笔误,重新提交。
唯一一篇非贪心题解。
UVA10276 Hanoi Tower Troubles Again!
前置知识:P2764 最小路径覆盖问题。
双倍经验:P2765 魔术球问题。这俩编号怎么只相差一。
分析
Part 1:如何建模
我们考虑对于数
可以发现这就是构成了一个有向无环图。
Part 2:如何根据球的数量计算柱子
其实,这就是一个最小路径覆盖。
让我们看样例的一个构造方案:
11
1 8
2 7 9
3 6 10
4 5 11
看出来了吗,这些颜色就是柱子上放的数!
当然,还有另一种构造方案:
其中 1 号点颜色不同。
Part 3:如何计算路径覆盖
由于我们懒得写网络流,于是我们翻出了匈牙利。
参见我写的:【最小路径覆盖问题】。
我们先进行拆点,将
根据 Dilworth 推广定理:最小路径覆盖数 = 顶点数 − 最大匹配数。
所以我们只需要跑二分图最大匹配了。
for(int i=0;i<m;i++){
int u,v;
cin>>u>>v;
G[u].push_back(v);//这里实际上是拆点了
}
为什么这里拆点了呢?其实左边的
Part 4:如何根据柱子的数量计算球
考虑二分球的数量。与
bool check(int mid){
if(solve(mid)<=n)return 1;
return 0;
}
int l=1,r=M,mid;
while(l<=r){
mid=(l+r)>>1;
if(check(mid))l=mid+1,now=mid;
else r=mid-1;
// cout<<l<<' '<<r<<endl;
}
if(check(now-1))now++;
if(!check(now-1))now--;
cout<<now-1<<endl;
我们发现边数大约为
当然你也可以不二分,直接枚举到
#include<bits/stdc++.h>
using namespace std;
const int maxn=10000;
bool matched[maxn];
vector<int> G[maxn];
int match[maxn];
bool find(int u){
for(auto v:G[u]){
if(!matched[v]){
matched[v]=1;
if(!match[v]||find(match[v])){
match[v]=u;
return 1;
}
}
}
return 0;
}
const int M=10000;
bool pd[M];
int solve(int ma){//根据球数算柱子
for(int i=0;i<maxn;i++)G[i].clear();
for(int i=1;i<=ma;i++){
for(int j=i+1;j<=ma;j++){
if((int)sqrt(i+j)*(int)sqrt(i+j)==i+j){
G[i].push_back(j);//这里实际上是拆点了
}
}
}
int ans=0;
memset(match,0,sizeof(match));
for(int i=1;i<=ma;i++){
memset(matched,0,sizeof(matched));
if(find(i))ans++;
}
return ma-ans;
}
int pre[maxn],nxt[maxn],n;
bool vis[maxn];
bool check(int mid){
if(solve(mid)<=n)return 1;
return 0;
}
signed main(){
int now=1;
cin>>n;
int l=1,r=M,mid;
while(l<=r){
mid=(l+r)>>1;
if(check(mid))l=mid+1,now=mid;
else r=mid-1;
// cout<<l<<' '<<r<<endl;
}
if(check(now-1))now++;
if(!check(now-1))now--;
cout<<now-1<<endl;
// for(int v=1;v<=now-1;v++){
// int u=match[v];
// if(u){
// nxt[u]=v;
// pre[v]=u;
// }
// }
// memset(vis,0,sizeof(vis));//标记顶点是否已被包含在路径中
// vector<vector<int>>paths;//存储最终的所有路径
// for(int u=1;u<=now-1;u++){
// if(pre[u]==0){
// vector<int> path;
// int v=u;
// while(v){
// vis[v]=1;
// path.push_back(v);
// v=nxt[v];
// }
// paths.push_back(path);
// }
// }
// for(auto &path:paths){
// for(int i=0;i<path.size();i++){
// cout<<path[i]<<' ';
// }
// cout<<endl;
// }
return 0;
}
Part 5:优化
还是上网络流。这样单轮时间复杂度是
#include<bits/stdc++.h>
using namespace std;
const int N=5055,M=125005,inf=2147483647;
int m,s,t;
int lvl[N];//层数
struct edge{
int u,v,w;
int nxt;
}ed[M*2];
int head[N],cur[N],cnt=2;
void adde(int u,int v,int w){
ed[cnt].u=u;
ed[cnt].v=v;
ed[cnt].w=w;
ed[cnt].nxt=head[u];
head[u]=cnt;
cnt++;
}
bool bfs(){
queue<int> q;
for(int i=0;i<N;i++)lvl[i]=-1;
lvl[s]=0;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=ed[i].nxt){
if(lvl[ed[i].v]==-1&&ed[i].w>0){
lvl[ed[i].v]=lvl[u]+1;
q.push(ed[i].v);
if(ed[i].v==t)return 1;
}
}
}
return(lvl[t]!=-1);
}
int dfs(int u,int cap){//cap:容量
int flow=0;//跑过的流
if(u==t)return cap;
for(int i=cur[u];i&∩i=ed[i].nxt){
cur[u]=i;
int v=ed[i].v,w=ed[i].w;
if(w&&lvl[v]==lvl[u]+1){
int use=dfs(v,min(cap,w));
if(use==0)lvl[v]=-1;
if(use>0){
ed[i].w-=use;
ed[i^1].w+=use;
cap-=use;
flow+=use;
}
}
}
return flow;
}
int dinic(){
int ans=0;
while(bfs()){
memcpy(cur,head,sizeof(head));
ans+=dfs(s,inf);
}
return ans;
}
const int MM=2000;
bool pd[M];
int solve(int ma){//根据球数算柱子
s=0,t=ma*2+1;
cnt=2;
memset(head,0,sizeof(head));
memset(cur,0,sizeof(cur));
for(int i=1;i<=ma;i++){
adde(s,i,1);
adde(i,s,0);
adde(i+ma,t,1);
adde(t,i+ma,0);
for(int j=i+1;j<=ma;j++){
if((int)sqrt(i+j)*(int)sqrt(i+j)==i+j){
adde(i,j+ma,1);
adde(j+ma,i,0);
}
}
}
return ma-dinic();
}
int pre[M],nxt[M],n;
bool vis[M];
bool check(int mid){
if(solve(mid)<=n)return 1;
return 0;
}
signed main(){
int now=1;
cin>>n;
int l=1,r=MM,mid;
while(l<=r){
mid=(l+r)>>1;
if(check(mid))l=mid+1,now=mid;
else r=mid-1;
// cout<<l<<' '<<r<<endl;
}
if(check(now-1))now++;
if(!check(now-1))now--;
cout<<now-1<<endl;
return 0;
}