20200724比赛总结

starseven

2020-07-28 10:16:03

Personal

这是省选范围的比赛,可是这一次的比赛考的比较简单,但还是有许多值得学习的地方,所以我在这里写下比赛总结。 $$\text{\color{red}Talk\; is\;cheap,show\;my\;summary.}$$ -------------- [T1](https://www.maipdf.com/pdf/?email=jem2Yq18UeUU.z) 这一道题题目还是有趣,在没有想到的时候我一度以为这是**变异的二分图匹配**。可是钱与钱之间可以进行**拆分**,即对于一个欠钱的人,ta可以同时还$i(i\geq 1)$个人的钱,与此同时,对于一个债主,ta可以同时接受$k(k\geq 1)$个人的还钱。 考虑题目说所有的人不关心自己是从哪里拿到的钱,而是拿没拿到应得的钱,所以我们可以开两个数组表示欠钱的人该换多少,债主该拿多少,简化问题后再做分析。 现在我们来考虑性质。 ### 前提 我们设集合$S$为借钱的人的集合,集合$T$为债主们的集合。集合存的是该还多少钱|该拿多少钱。 设一个集合的$|S|$表示集合$S$中点权值之和。 引理: 1. 选取两个权值和相等的子集总是比不相等的优秀 这个引理用数学语言证明起来很麻烦,所以我们可以大致理解一下。 因为相等的话所有的连边都是在内部进行,但是不相等的话就至少有一条边伸到外部去。 2. 选取两个集合,总数目越小的越优 因为对于两个相等的集合,他们所对答案产生的贡献为$size(S\rq)+size(T\rq)-1$ 有人说,这幅图就直接推翻了你的理论啊! ![muban.png](https://i.loli.net/2020/07/28/xKmufGW1nEdBRFI.png) 可是这明显是当时思维不深的表现,但需要注意的是**思考不缜密或者思考不深**都会让一些转化性质的题直接暴毙。 对于上面的一幅图,明显违背了我们的假设 因为这个集合内部本身就有**小型的,满足条件**的集合 ![muban.png](https://i.loli.net/2020/07/28/Bl3HujyqMTvOG5d.png) 悖论消失 -------------------- ## 阐述 设$S\rq \subseteq S\;\;$,$\;\;T\rq \subseteq T$ - 如果$|S\rq|\neq |T\rq|$ 那么无论如何,总有一个集合的点权值没法用完,需要到外面去。相当于消去两个集合后增加了一个点。可能是最优的选择,但绝大多数不是最优的选择。(预知后事如何,且听下面分解) - 如果$|S\rq|= |T\rq|$ 那么集合内部就可以约去,而上面说的**可能是最优**就是你误打误撞将一个相等的子集的子集给分解了。 所以我们就可以拿出这个像是**贪心**,像是**dp**的东西了。 ```cpp #include<cstdio> #include<algorithm> #define ri register int #define Starseven main using namespace std; const int N=30; int read(); void write(int);//要记得自己加换行和空格 int n,m,zhuang; int cost[N]; int out[N],in[N],numout,numin,ans,num; bool visout[N],visin[N]; struct node{ int id,val; }bag[1<<22]; void Init(void){ freopen("ex_bill3.in","r",stdin); return ; } int Get_bag(int x){ int num=1,re=0; while(x){ if(x&1) re+=in[num]; x>>=1;num++; } return re; } bool cmp(const node &a,const node &b){ if(a.val!=b.val) return a.val<b.val; return a.id>b.id; } bool Is(int x){ int num=1; while(x){ if(x&1) if(visin[num]) return false; x>>=1;num++; } return true; } int Find(int x){ int l=1,r=zhuang; while(l<r){ int mid=l+r>>1; if(bag[mid].val>=x) r=mid; else l=mid+1; } if(bag[l].val!=x) return 0; while(!Is(bag[l].id)&&bag[l].val==x) l--; while(!Is(bag[r].id)&&bag[r].val==x) r++; if(bag[r].val==x) return r; if(bag[l].val==x) return l; return 0; } bool Dfs(int x,int re,int st){ if(x==0){ int j=Find(re); if(j==0) return false; int y=bag[j].id,num=1,cnt=0; while(y){ if(y&1) visin[num]=true,cnt++; y>>=1;num++; } // cout<<st<<" "<<cnt<<endl; ans+=st+cnt-1; return true; } for(ri i=1;i<=numout;i++){ if(visout[i]) continue; visout[i]=true; bool ok=Dfs(x-1,re+out[i],st); if(!ok) visout[i]=false; else return true; } return false; } int Starseven(void){ // Init(); n=read(),m=read(); for(ri i=1;i<=m;i++){ int ai=read(),bi=read(),ci=read(); cost[ai]-=ci;cost[bi]+=ci; } for(ri i=1;i<=n;i++) if(cost[i]<0) out[++numout]-=cost[i]; else if(cost[i]>0) in[++numin]+=cost[i]; if(numout>numin) swap(numout,numin),swap(out,in); sort(out+1,out+1+numout);sort(in+1,in+1+numin); zhuang=1<<numin;zhuang--; for(ri i=0;i<=zhuang;i++) bag[i].val=Get_bag(i),bag[i].id=i; sort(bag+1,bag+zhuang+1,cmp); for(ri i=1;i<=numout;i++){ bool ok=true; while(ok==true) ok=Dfs(i,0,i); if(num==numin) break; } write(ans); return 0; } int read(){ char ch=getchar(); int re=0,op=1; while(ch<'0'||ch>'9'){ if(ch=='-') op=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ re=(re<<3)+(re<<1)+ch-'0'; ch=getchar(); } return re*op; } void write(int x){ if(x<0){ putchar('-'); x=-x; } if(x>9) write(x/10); putchar(x%10+'0'); return ; } /* 4 4 1 2 1 2 3 1 3 4 1 4 1 1 */ /* 4 3 1 2 1 2 3 2 3 4 3 */ ``` 这是本人在考场上打的代码,与正解有一点不同,现在将正解展示 标程如下 ```cpp #include<bits/stdc++.h> using namespace std; const int N=1010; int a[N],c[N],t[N]; int n,m; int cmax; int ans=0; int read(){ int s=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();} while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar(); return s*w; } void dfs(int x,int num,int val){ if(num>cmax) return; t[num]=x; if(val==0){ if(num<cmax){ // cout<<"qwq"<<cmax<<" "<<num<<" "; memset(c,0,sizeof(c)); cmax=num;//cout<<cmax<<endl; for(int i=1;i<=num;i++) c[i]=t[i]; return; } else return; } for(int i=x+1;i<=n;i++) if(a[i]!=0){ dfs(i,num+1,val+a[i]); } t[num]=0; } int work(int u){ memset(c,0,sizeof(c));//最终结果 memset(t,0,sizeof(t)); cmax=N; dfs(u,1,a[u]); for(int i=1;i<=cmax;i++) a[c[i]]=0; return cmax; } int main(){ // freopen("ex_bill1.in","r",stdin); n=read(),m=read(); for(int i=1;i<=m;i++){ int u=read(),v=read(),w=read(); a[u]-=w;a[v]+=w; } ans=0; for(int i=1;i<=n;i++){ if(a[i]!=0){ ans+=work(i)-1; } } printf("%d\n",ans); return 0; } ``` ![H@DKJQQ_GL`D_471C3B155U.png](https://i.loli.net/2020/07/28/sEwQn3V26LfkWJ8.png) 红色为我的代码,绿色为标程,慢不了多少的,对吧。 (~~暴力还是太惨了~~) ----------------------- [T2](https://pdf.maitube.com/pdf/?e=jgIShPWgG.u9Ut) 这道题很值得做一下,它本身不需要太多的知识,或者换而言之,它基本不需要知识(对于省选来说,甚至是对于提高组来说),但是这个题目本身的思路是很值得学习的。 有人会说:这不就是基环树模板题吗? 但是对不对,只要勤动脑子,这题不需要基环树的基础。 但是还是要学习基环树,有模板可套谁想要去想啊qwq --------------- 题目分析: