[考试反思]0130省选模拟13:悔恨
0+30+20=50。rk8
关键在于:
T1写错个变量名挂了100分
kxkxkxkx(激动的语无伦次)
考试过程?上来看T1,一个比较裸的基环树dp,断开环上一条边之后大概就是一个稍加改动的最大独立集。
思路不难想,细节倒是有一点,考场上调啊调啊过了样例又手模了各种数据都过了两个半小时之后很开心就扔了。
然后看T2/3啥都不会,写了俩随机化就走了。
然而T3随机化+贪心是可以AC的。正确性不知道。。。没写。。。
最后还有5分钟回T1,突然发现T1可以不是基环树而是基环树森林???
啊修锅修锅5分钟生死时速改完了最后测了遍样例,过了。
md样例不是森林啊喂它就是个基环树啊!!!
结果有一个变量写错了,在全图联通的情况下不会错。
然后友善的数据里一棵树都没有,光荣爆零。
然而T2的随机化又交一遍,多了10分。这脸黒的。。。
考后一直认为T1恶心而没有改,然而直到晚上剩下两道题改完我拿回T1代码之后感觉变量名不太对劲。。。3分钟后就AC了。。。
考后改题极度不顺利。其实主要是顺序错了,上来直接奔着T2的正解去了,结果一下午都没弄出来。
晚饭前寻思着换一下思路于是去写T3,10分钟,1A。
然而在其他人视角看来:一下午没有AC。我也很难受啊啊啊(写个P正解$O(2^8 \times 8 \times n)$理论复杂度都能过为啥要写正解)
然而至少这一次不容易是被认为在颓废了。T2一道题50个提交记录也不是闹着玩的。。。
就这样吧。一整天在这个椅子上坐了多少个小时一动没动。。。修仙一样。。。
T1:同桌的你
题目大意:黑白点基环森林。选定边集,要求选定后每个点度数为1,最大化边数的基础上最大化连接异色点的边数。要求输出任意一种方案。$n \le 10^6$
先假如它们都联通,是一棵基环树的话,怎么做?
我不喜欢pair,所以我设异色边的贡献为1000002,同色边的贡献为1000001,最大化贡献。最后答案除1000001就是边数,模1000001就是异色边数。(然而要开long long)
这样之后就变成了带权匹配问题。
常用的对付基环树的思路就是断开环上的一条边剩下一棵树做问题,然后再单独考虑这条边的贡献。
于是断开这条边之后这就真的变成了最大树上带权匹配,直接dp就可以了。dp[0/1][p]表示这个点有没有被它儿子匹配时的最优值。
然后考虑拆掉的一条边的贡献,发现它的贡献与它所连接的两个点是否匹配有关。
比较方便的做法就是把其中一个点当作根,然后再开一个[0/1]维表示那个节点是否被选。特殊处理一个点,不算太麻烦。
至于要求输出方案。。。变化不大,记录一下如果这个点和它的某个儿子配对了,那它到底是和那个儿子配对的,然后就可以dfs下去了(不管是否与儿子配对了都往下搜就是了)。
至于不是基环树而是基环森林。。。当成多个基环树分别跑就好了。
然后这题就没了啊。就是有点细节难写了一点而已。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 2222222 4 #define C cp[sp] 5 int cp[S],n,sex[S],fir[S],l[S],to[S],ec,deg[S],q[S],T,t,sp,sel[2][S];long long tans,dp[2][2][S],ans; 6 vector<int>v1,v2; 7 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 8 void dfs(int p,int fa){ 9 dp[0][0][p]=dp[1][0][p]=0;deg[p]=0; 10 for(int i=fir[p];i;i=l[i])if(to[i]!=fa)dfs(to[i],p); 11 for(int q=0;q<2;++q){ 12 long long md=-123456789098765,tot=0;int mp=0; 13 for(int i=fir[p];i;i=l[i])if(to[i]!=fa){ 14 dp[q][0][p]+=max(dp[q][0][to[i]],dp[q][1][to[i]]); 15 if((to[i]!=sp||(to[i]==sp&&q==1))&&-max(dp[q][0][to[i]],dp[q][1][to[i]])+dp[q][0][to[i]]+1000001+(sex[p]^sex[to[i]])>md) 16 md=-max(dp[q][0][to[i]],dp[q][1][to[i]])+dp[q][0][to[i]]+1000001+(sex[p]^sex[to[i]]),mp=to[i]; 17 }if(mp)sel[q][p]=mp,dp[q][1][p]=dp[q][0][p]+md; 18 }if(p==sp)dp[1][0][p]=dp[0][0][p],dp[0][1][p]=-123456789098765; 19 } 20 void sch(int s1,int s2,int p,int fa=0){ 21 if(p==sp&&s1==1&&s2==0)s1=0; 22 for(int i=fir[p];i;i=l[i])if(to[i]!=fa) 23 if(s2&&to[i]==sel[s1][p])v1.push_back(p),v2.push_back(to[i]),sch(s1,0,to[i],p); 24 else sch(s1,dp[s1][0][to[i]]<dp[s1][1][to[i]],to[i],p); 25 } 26 int main(){//freopen("1.in","r",stdin); 27 cin>>T;while(T-->0){ 28 t=sp=ans=0;memset(dp,0xa0,sizeof dp);for(int i=1;i<=n;++i)deg[i]=sel[0][i]=sel[1][i]=0; 29 cin>>n;for(int i=1;i<=n;++i)scanf("%d%d",&cp[i],&sex[i]),sex[i]--,deg[cp[i]]++; 30 for(int i=1;i<=n;++i)if(!deg[i])q[++t]=i; 31 for(int h=1;h<=t;++h){deg[cp[q[h]]]--;if(!deg[cp[q[h]]])q[++t]=cp[q[h]];} 32 reb:sp=0; 33 for(int i=1;i<=n;++i)if(deg[i])sp=i; 34 if(!sp)goto res; 35 for(int i=1;i<=n;++i)fir[i]=0;ec=0; 36 for(int i=1;i<=n;++i)if(i^sp)link(i,cp[i]),link(cp[i],i); 37 dfs(C,0); 38 tans=max(dp[0][0][C]+1000001+(sex[C]^sex[sp]),max(dp[1][0][C],max(dp[0][1][C],dp[1][1][C]))); 39 if(tans==dp[0][0][C]+1000001+(sex[C]^sex[sp]))v1.push_back(C),v2.push_back(sp),sch(0,0,C); 40 else if(tans==dp[1][0][C])sch(1,0,C); 41 else if(tans==dp[1][1][C])sch(1,1,C); 42 else if(tans==dp[0][1][C])sch(0,1,C); 43 ans+=tans; 44 goto reb; 45 res: 46 cout<<ans/1000001<<' '<<ans%1000001<<endl; 47 for(int i=0;i<v1.size();++i)printf("%d %d\n",v1[i],v2[i]); 48 v1.clear();v2.clear(); 49 } 50 }
T2:大水题
大意:坐标轴上n个点,每个点有颜色(一共8种)。求最长子段,两端点都是给定点,且段中至少有$k$种颜色,且每种出现过的颜色出现次数相同。$n \le 10^5$
优秀一点的暴力的话,运用到差分。考虑怎么应用出现的次数都相等这个性质。
做差之后都一样啊不久相等了吗?所以我们排序后扫一遍序列统计每种颜色出现次数的前缀和,每次枚举一个集合,然后做差哈希压状态,在每个点都更新一次答案或数组。
对于满足条件的段的两个端点,它们的哈希值是一样的。维护dp[s][hsh]表示到目前点为止,选定颜色集为s此时差分的哈希值为hsh的这种状态的最早出现位置。
要考虑到如果出现了一个选定点集中不包含的颜色,那么就要把对应的点集清空。
这样做的复杂度是$(8 \times 2^8 \times n)$。然而跑不满,极致卡常之后,就可以AC了。
细节倒是有不少。数据特别水动不动就WA90。什么哈希撞模撞进制,每次断开时要记得把桶清空,还要插一个哈希为0当前点的位置 。。。
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct cow{int x,c;friend bool operator<(cow a,cow b){return a.x<b.x;}}c[111111]; 4 vector<int>ve,v[1<<8];int cnt[9][111111],k,n,ans=-1,rj[1<<8],sc,sn[1<<8],C; 5 struct hashmap{ 6 #define mo 10000003 7 unsigned long long to[mo];int fir[mo],l[mo],v[mo],ec; 8 int&operator[](unsigned long long x){ 9 int r=x%mo; 10 for(int i=fir[r];i;i=l[i])if(to[i]==x)return v[i]; 11 l[++ec]=fir[r];fir[r]=ec;to[ec]=x;if(!l[ec])ve.push_back(r);return v[ec]=-1; 12 } 13 void clear(){ 14 ec=0;for(int i=0;i<ve.size();++i)fir[ve[i]]=0;ve.clear(); 15 } 16 }M; 17 unsigned long long m3[9]; 18 int cal(int l,int r){ 19 int c=0; 20 for(int i=1;i<=C;++i)c+=(cnt[i][r]-cnt[i][l]?1:0); 21 return c>=k; 22 } 23 24 int main(){//freopen("d.in","r",stdin);freopen("1.out","w",stdout); 25 cin>>n>>k; 26 for(int i=1;i<=n;++i)scanf("%d%d",&c[i].x,&c[i].c),cnt[c[i].c][i]++,C=max(C,c[i].c); 27 for(int i=1;i<=C;++i)for(int j=1;j<=n;++j)cnt[i][j]+=cnt[i][j-1]; 28 for(int i=1;i<1<<C;++i){ 29 int j,cb=1; 30 for(j=C;j;--j)if(i&1<<j-1)break;rj[i]=j; 31 for(j--;j;--j)if(i&1<<j-1)cb++,v[i].push_back(j); 32 if(cb>=k)sn[++sc]=i; 33 } 34 sort(c+1,c+1+n); 35 for(int q=1,s;s=sn[q],q<=sc;++q){ 36 unsigned long long hsh=0,rt=1;for(int i=1;i<=C;++i)m3[i]=0;M.clear();M[0]=0; 37 for(int i=0;i<v[s].size();++i)m3[v[s][i]]=rt,rt*=311321; 38 for(int i=1;i<=n;++i)if(c[i].c==rj[s]){ 39 unsigned long long p=1;while(p!=rt)hsh-=p,p*=311321; 40 if(M[hsh]!=-1)ans=max(ans,cal(M[hsh],i)*c[i].x-c[M[hsh]+1].x); 41 else M[hsh]=i; 42 }else if(m3[c[i].c]){ 43 hsh+=m3[c[i].c]; 44 if(M[hsh]!=-1)ans=max(ans,cal(M[hsh],i)*c[i].x-c[M[hsh]+1].x); 45 else M[hsh]=i; 46 }else M.clear(),M[0]=i,hsh=0; 47 }cout<<ans<<endl; 48 }
说一下尝试了一下午最后放弃了的正解。
不难发现每次枚举所有的256种状态明显有多余,考虑什么是真正有效的状态。
加入当前在点$i$,那么点$i-2$选了的话点$i-1$也一定选了,所以说这是单调的,往前走颜色的种数单调递增。
这样的话其实每个点的有效状态只有$8$个。
然后有一个细节不是很好处理,$skyh$比较忙丢给我一份代码我没看懂。。。所以只能自己口胡一个没写出来的。
就是你会发按照这样只枚举8种状态的话,现在某种奶牛出现的时候有一些状态丢失了。(例如样例,你在第7个点尝试更新答案时,你想找集合(1 2 3)却发现你在对应的第1个点处时并没有(1 2 3)这个集合)
怎么办?最简单粗暴的就是在更新每个状态时,把它的每一个超集也更新,但是这样复杂度是不对的。
然而一个可行的复杂度正确的方法是:如果一种颜色是第一次出现,那么就把所有已有状态(一定都不包含它,因为是第一次出现)都加上这种颜色,拷贝一遍。
这样就能解决上述问题了。因为最多只有8次这种操作所以复杂度不是问题。
然而实现起来挺恶心的,拷贝的时候需要处理哈希值的变化(插入了一种数),分类讨论什么的。。。非常麻烦于是没有写
T3:佛罗里达
大意:$n$点分成两部,$i,j$在同一部有$T_{i,j}$代价。最小化两部中最大代价的和。$n \le 250$
一眼网络流。错误直觉害人啊。
先扯扯随机化。
考虑一个贪心,我们依次考虑每个点,把它加入一个集合,让代价尽量小。
这显然是有后效性的,所以没有正确性。毕竟只是一个假贪心。
但是我们发现如果加入的点的顺序比较合适我们就能找到最优解。而且概率并不是很小(不会算也不会证)
所以我们把点随机排列进行贪心,单次复杂度是$O(n^2)$的。由于贪心的存在正确率还不错,所以就AC了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int mtx[255][255],v[255],n,ec,f[255],ans,q1[255],q2[255],f1,f2,t1,t2; 4 struct ed{int a,b,v;friend bool operator<(ed x,ed y){return x.v<y.v;}}e[66666]; 5 int find(int p){return f[p]==p?p:f[p]=find(f[p]);} 6 int main(){//freopen("1.in","r",stdin); 7 int T=0; 8 while(T++,cin>>n){ 9 for(int i=1;i<=n;++i)f[i]=i;ec=0;ans=2000000000; 10 for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j)scanf("%d",&mtx[i][j]),mtx[j][i]=mtx[i][j]; 11 srand(time(0)); 12 while(clock()*(3-T)<2900000){ 13 random_shuffle(f+1,f+1+n);t1=t2=f1=f2=0; 14 for(int i=1;i<=n;++i){ 15 int fee1=f1,fee2=f2,p=f[i]; 16 for(int j=1;j<=t1;++j)fee1=max(fee1,mtx[p][q1[j]]); 17 for(int j=1;j<=t2;++j)fee2=max(fee2,mtx[p][q2[j]]); 18 if(fee1-f1>fee2-f2)f2=fee2,q2[++t2]=p; 19 else f1=fee1,q1[++t1]=p; 20 }ans=min(ans,f1+f2); 21 }cout<<ans<<endl; 22 } 23 }
最后看了正解才发现它和我考场上的思路有多像。
首先这个东西让我们求最小值让人总想二分。但是因为有个加和所以不能直接搞。
因为数据范围不大,所以我们考虑枚举其中一部的贡献,再二分检查另一部。
所以问题转化为A部和B部的内部连边分别小于等于某个值是否可行。
根据边与这两个值的大小关系分类讨论,可以发现这个分部其实就是2SAT的模型。
总的权值数是$O(n^2)$的。单次处理是$O(n^2log)$的。所以总的复杂度是$O(n^4log)$的。
没多少分所以考场上没有写。只比暴力多了20分然而没剩下多少时间很可能调不出来。
这样枚举这种图上最大值的模型取值不会很多,在这道题里只有$O(n)$种。(不会证)
所以猜测是最大生成树上的边,根据题目定义发现这是对的。
跑最大生成树,然后进行黑白染色(这里考场上没有考虑到)。
如果一种划分同时包含了黑点与白点,那么它的取值就一定是最大生成树上的边了。
而如果它只包含了一种颜色的点,那么只有1种情况,特殊处理即可。
然后这样总的复杂度就下降到了$O(n^3log)$。看起来玄但是出题人说是可以过的。
然而考场上考虑的不全面,时间也不够,复杂度看起来也不对,就没敢写。
最近总是差不多想到正解还是没敢写啊。。。还是太菜了,继续加油啊。。。