目录
- CF 694 Div.1 D *1900
- CF1418G Three Occurrences *2500 异或哈希+双指针
- CF2014H *2000
- ABC367F Rearrange Query
- CF938C *1700
- CF1418D *2100
- CF1732C2 *2100
- CF1732 A
- CF845G *2000
- CF1732D1 *1500
- CF1732D2 *2400
- CF1795E *2200 数据结构优化dp
- CF1221D *1800
- CF2052F *1700
- CF2045G *2200
- CF1598F RBS *2400 状压dp
- D2. 388535 (Hard Version) *2300 思维 2种做法
- CF2041H *2300 DP
- CF1492D *1900
CF 694 Div.1 D *1900
\[\begin{flalign*}
&给定一个图,求是否存在染色方案,满足:\\&
1.相邻边不能同时染黑色&\\&
2.如果相邻边同时白色,则断开\\&
3.染完的图连通\\&
联想到二分图染色,所以写了二分图判定\\&
但是这并不是二分图,因为1和1不能相邻,但2和2可以相邻\\&
所以错了,其实只要暴力染色即可
\end{flalign*}
\]
void dfs(int u){color[u]=1;for(int v:g[u]) if(color[v]==1) color[u]=2;for(int v:g[u]) if(!color[v]) dfs(v);
}
dfs(1);这样写既能判断连通性,而且能染色,每染好一个1,周围都染2,每个2周围未被访问过的点再染1
CF1418G Three Occurrences *2500 异或哈希+双指针
\[\begin{flalign*}
&给定一个序列,求满足:每个数出现次数都是3的连续子序列数量&\\&
思路:\\&(1)我们怎么快速判断一个数在之前有没有出现过呢?\\&
我们可以给这个数赋随机异或一个值,然后维护前缀和\\&
如果发现当前的前缀和在之前也出现过,就可以发现当前这个区间的数在之前也一定出现过\\&
考虑异或哈希,对于一个数,出现3k,3k+1,3k+2次的时候分别赋值为a、b和a\wedge b\\&
那么s_{l-1}\wedge s_{r}和为0的区间中每个数的出现次数都是3k次\\&
用map维护每个在前缀异或和在[1,r]出现了多少次\\&
区间[l,r]合法等价于s_{l-1}=s_r\\&
(2)此时考虑如何限制每个数出现次数正好是3\\&
用双指针,每次统计完右端点为r的情况,移动左指针l直到当前数出现正好4次\\&
然后再往右移动一次,那么此时的[l,r]中a_r正好出现3次\\&
那么我们判断[l,r]中合法左端点,需要保留[l-1,r-1]的前缀异或和\\&
所以,[0,l-2]的所有答案都不做贡献了\\&
那么可以保证不会出现右端点数出现3次,而中间数字出现3次以上的情况了\\&
因为前面的数字已经统计完了,左指针必定右移到了一定地步,保证之后的统计不会被影响
\end{flalign*}
\]
int a[N],c[N],d[N],h[N],cnt[N];
mt19937_64 rnd(1e18);
void solve(){for(int i=1;i<N;i++) c[i]=rnd(),d[i]=rnd();cin>>n;for(int i=1;i<=n;i++){cin>>a[i];cnt[a[i]]++;if(cnt[a[i]]%3==0) h[i]=c[a[i]];else if(cnt[a[i]]%3==1) h[i]=d[a[i]];else h[i]=c[a[i]]^d[a[i]];}for(int i=1;i<=n;i++){cnt[a[i]]=0;}map<int,int>mp;mp[0]=1;int ans=0;vector<int>sum(n+1,0);for(int l=0,r=1;r<=n;r++){sum[r]=sum[r-1]^h[r];cnt[a[r]]++;while(cnt[a[r]]>3){cnt[a[l]]--;if(l) mp[sum[l-1]]--;l++;}ans+=mp[sum[r]];mp[sum[r]]++;}cout<<ans;
}
\[\begin{flalign*}&对异或哈希做一个总结&\\&可以维护区间数出现出现是否是k次\\&维护区间数出现次数都是偶次也可以莫队\\&
\end{flalign*}
\]
CF2014H *2000
\[\begin{flalign*}
&判断区间数出现次数是否都为偶&\\&
要点是赋随机值后再哈希,不然冲突率很高
\end{flalign*}
\]
ABC367F Rearrange Query
\[\begin{flalign*}
&给定2个序列,q次询问:a_{l_i}-a_{r_i}和b_{L_{i}}-b_{R_{i}}构成的集合是否相同&\\&
异或哈希pending显然会冲突,所以使用单模加法哈希
\end{flalign*}
\]
CF938C *1700
\[\begin{flalign*}
&构造,n*n的01矩阵的任意大小为m*m的子矩阵1个数最多&\\&
最多为n^2-(\frac{n}{m})^2,取右下角\end{flalign*}
\]
CF1418D *2100
\[\begin{flalign*}
&题意:给你n个不同的坐标x_i,x_i上有物品,将a移动到b的花费为,操作完后a无物品\\&
求将所有物品移动到1或2个定点上的最小花费&\\&
q次更新,增查物品坐标,询问修改完移动总花费最小值\\&
解析:先考虑定点怎么取?如果取一个点,那么总花费会是max-X+X-min=极差\\&
如果取两个点X和Y(X<Y),最小花费为max-Y+X-min\\&
那么必然取两个点更优,最优的时候,必定是Y-X最大化的时候\\&同时Y-X必定是存在的x的差值\\&
同时需要特判点数过少情况\\&
所以我们需要一个维护x_{i+1}-x_i最大最小值的数据结构,使用multiset\\&
!!!注意prev和next用法,begin()不能prev
\end{flalign*}
\]
CF1732C2 *2100
\[\begin{flalign*}
&题意:给你一个序列,值域10^9,q次询问:&\\&
每次询问[L,R]中长度最小的区间[l,r]使得sum(l,r)最大\\&
sum(l,r)=\sum_{i=l}^{r}a_i-{\textstyle xor_{l}^{r}}a_i\\&
思路:a+b>=a\oplus b\\&
sum(l,r)\le sum(l,r+1)\\&
所以最大值就是sum(L,R)\\&
找一个最小区间使得sum(l,r)=sum(L,R)\\&
又因为值域是[0,10^9]\\&
去除的数字满足\sum X=xor X,根据抽屉原理,不超过三十个数,由此暴力枚举\\&
不过需要 离散化除去0,不然复杂度太高或者答案错误!\\&
同时需要判断询问区间全0的特殊情况!!\\&
减法运算优先级大于xor
\end{flalign*}
\]
void solve(){int q; cin>>n>>q;vector<int>a(n+1,0),b(n+1,0),c(n+1,0),d(n+20,0);int cnt=0;for(int i=1;i<=n;i++){cin>>a[i];b[i]=b[i-1]+a[i];c[i]=c[i-1]^a[i];if(a[i]>0) d[++cnt]=i;}d[++cnt]=n+100;auto calc=[&](int l,int r){return (b[r]-b[l-1]-(c[r]^c[l-1]));};for(int i=1;i<=q;i++){int ll,rr; cin>>ll>>rr;if(cnt==1){cout<<ll<<' '<<ll<<endl; continue;}auto l=lower_bound(d.begin()+1,d.begin()+cnt+1,ll)-d.begin();auto r=lower_bound(d.begin()+1,d.begin()+cnt+1,rr)-d.begin();if(d[l]>rr){cout<<ll<<' '<<ll<<endl; continue;}if(d[r]>rr) r--;int temp=calc(ll,rr);array<int,3> ans={rr-ll+1,ll,rr};for(int i=l;i<=min(l+30,r)&&d[i]<=rr;i++){for(int j=r;j>=max(l,r-30)&&d[j]>=d[i];j--){if(calc(d[i],d[j])==temp){ans=min(ans,{d[j]-d[i]+1,d[i],d[j]});}}}cout<<ans[1]<<' '<<ans[2]<<endl;}
}
CF1732 A
\[\begin{flalign*}
&题意:给你一个序列,你可以选一个a_i将他变成gcd(a_i,i),花费是n-i+1&\\&
求所有数gcd=1的最小花费\\&
考虑到一次操作等价于总gcd=gcd(g,i)\\&
最劣情况是取最后两位花费3,不然就是只要改一次,枚举check即可
\end{flalign*}
\]
CF845G *2000
\[\begin{flalign*}
&图论+线性基典题&\\&
\end{flalign*}
\]
CF1732D1 *1500
\[\begin{flalign*}
&题意:给你一个初始只有0的集合&\\&
操作1:集合中加一个数x\\&
操作2:查询最小的未出现的x的倍数\\&
思路:last[x]代表x的答案是last[x]+x,查询暴力跳即可\\&
复杂度说明:设集合中有2e5个数,查x需要k次,那么查2x要k/2次,以此类推,上界为O(NlogN)
\end{flalign*}
\]
CF1732D2 *2400
\[\begin{flalign*}
&题意:给你一个初始只有0的集合&\\&
操作1:集合中加一个数x\\&
操作2:删除x\\&
操作3:查询最小的未出现的x的倍数\\&
思路:在easy\ ver的基础上,如何利用查询mex跳过的已经存在的数?\\&
若我们删除了v跳过的数字x,那么下一次查v的答案就是x\\&
在这个基础上,除了需要map<int,int>vis和last\\&
还需要map<int,set<int>>mul维护x的候选答案\\&
map<int,vector<int>>维护删除x会影响哪些数的答案\\&\end{flalign*}
\]
void solve(){map<int,vector<int>>fac;map<int,set<int>>mul;map<int,int>last,vis;cin>>n;vis[0]=1;for(int i=1;i<=n;i++){char c;int v;cin>>c>>v;if(c=='+'){vis[v]=1;for(auto u:fac[v]){mul[u].erase(v);}}else if(c=='-'){vis[v]=0;for(auto u:fac[v]){mul[u].insert(v);}}else{if(mul[v].size()){cout<<*mul[v].begin()<<endl;continue;}while(vis[last[v]]){fac[last[v]].push_back(v);last[v]+=v;}cout<<last[v]<<endl;}}
}
CF1795E *2200 数据结构优化dp
\[\begin{flalign*}
&题意:n个怪,每个怪血量h_i,两种操作&\\&
1.花费1,让一个怪血量少1\\&
2.花费h_i,左右两侧受到h_i-1伤害,如果左/右死亡,则继续传递\\&
问消灭所有怪的最小总花费是多少?\\&
思路:肯定是能减则减,然后做一次爆炸这样最少\\&
考虑暴力枚举右端点i,那么每次往左跳到第一个小于他的下标j\\&
那么[j+1,i]的最小花费是h_i+等差数列之和\\&
由此可以正向dp,和单调栈一起跑就行
\end{flalign*}
\]
int h[N],stk[N];
int top;
void solve(){cin>>n;int sum=0;vector<int>l(n+1),r(n+1);for(int i=1;i<=n;i++){cin>>h[i];sum+=h[i];}top=0;for(int i=1;i<=n;i++){while(h[stk[top]]-stk[top]>=h[i]-i&&top) top--;if(!top) l[i]=(h[i]+max(1ll,h[i]-i+1))*(h[i]-max(1ll,h[i]-i+1)+1)/2;else l[i]=l[stk[top]]+(h[i]*2-i+stk[top]+1)*(i-stk[top])/2;stk[++top]=i;}reverse(h+1,h+1+n);top=0;for(int i=1;i<=n;i++){while(h[stk[top]]-stk[top]>=h[i]-i&&top) top--;if(!top) r[i]=(h[i]+max(1ll,h[i]-i+1))*(h[i]-max(1ll,h[i]-i+1)+1)/2;else r[i]=r[stk[top]]+(h[i]*2-i+stk[top]+1)*(i-stk[top])/2;stk[++top]=i;}int ans=1e18;for(int i=1;i<=n;i++){ans=min(ans,sum-l[i]-r[n-i+1]+2*h[n-i+1]);}cout<<ans<<endl;
}
CF1221D *1800
\[\begin{flalign*}
&题意:n个塔高度a_i,第i个塔增加高度1花费b_i,可以加或者无限加&\\&
问使得a_i不等于a_{i+1}的最小花费\\&
题解:关键点,每个点增加次数不超过2次\\&
最劣的时候是a_i=a_{i-1}=a_{i+1}-1这种情况\\&
设f[i][j]代表a_i变化为j前i个塔满足条件的最小花费\\&
状态转移:f[i][j]=min(f[i][j],f[i-1][k]+b[i]*j),j+a[i]!=a[i-1]+k\\&\end{flalign*}
\]
CF2052F *1700
\[\begin{flalign*}
&题意:给你一个2*n的有障碍的矩形,可以用任意1*2的矩阵填充剩下&\\&
没有障碍的地方,问你是否能填满?能填满的话是否有多种填发?\\&
要点!!手玩,可以直接模拟是否填满!!依次从左往右填,如果左边填好\\&
右边出现2*i(i\ge2)的矩形则多种方案
\end{flalign*}
\]
CF2045G *2200
\[\begin{flalign*}
&题意:给你一个n*m的矩形,相邻的格点连通\\&
i走到相邻格点j的花费是(h_i-h_j)^x,x是正奇数&\\&
q次询问:i到j的最小距离,如果是负无穷回答不合法\\&
思路:如果存在负环,显然不行,存在正环倒着走就是正环\\&
所以如果有环都得是0环,由必要性知,如果一个矩形中都是0环\\&
那么任取2*2的矩形都是0环的,因为这样任意环都可以分解成整数个2*2环\\&
如果存在一个2*2矩阵不是0环,那么可以构造出更大的来\\&
由此遍历2*2矩阵,存在非0环就不行\\&
距离有传递性,手玩一下即懂
\end{flalign*}
\]
CF1598F RBS *2400 状压dp
待补
D2. 388535 (Hard Version) *2300 思维 2种做法
\[\begin{flalign*}
&easy\ version题意:对于[0,r]的一个排列,每个数都异或x,给定异或好的序列&\\&
求出一个满足条件的,保证询问的序列长度和不超过2^{17}\\&
考虑枚举二进制位,对比序列和[0,l]的按位异或即可\\&
对于hard\ version\ 左端点不一定为0\\&
做法1:O(Nlog^2N)\\&
按照easy\ ver的做法有一种情况会不符合,就是区间长度是偶,且某些位置0/1数量正好是相等\\&
观察到easy \ ver的做法并没有很好利用到给定序列的信息,只是观察了单独第i位的整体信息\\&
偶数信息无法判断\\&
考虑递推,将x右移i位,即可得到x第[i,16]位上的信息\\&
考虑直接枚举这一位是0还是1,我们得到得到第[i,16]位的一个集合信息和原序列对比\\&
那么就能知道这一位是0/1的正确性了\\&
做法2:O(NlogN)\\&
利用异或的性质:\forall a \ne b \ 有a \wedge x \ne b \wedge x\\&
那么我们枚举l \wedge x,即枚举x=a_i \wedge l,如果x和序列中的数异或的最值分别为[l,r]\\&
那么就是一个正确答案,用01trie维护即可,最小值尽可能找一样的,最大值尽可能不一样
\end{flalign*}
\]
void solve(){int l,r; cin>>l>>r;n=r-l+1;for(int i=1;i<=n;i++){cin>>b[i];}array<vector<int>,17>tar;int x=0;for(int i=0;i<17;i++){for(int j=l;j<=r;j++){tar[i].push_back(j>>i);}sort(tar[i].begin(), tar[i].end());}for(int i=16;i>=0;i--){for(int t=0;t<2;t++){x^=1<<i;vector<int>nw;for(int j=1;j<=n;j++){nw.push_back((b[j]>>i)^(x>>i));}sort(nw.begin(), nw.end());if(nw==tar[i]){break;}}}cout<<x<<endl;
}
int s[N][2];
int cnt;
void insert(int k){int x=1;int id;for(int j=18;j>=0;j--){id=((k>>j)&1);if(!s[x][id]) s[x][id]=++cnt;x=s[x][id];}
}
int qmi(int k){//找最小int res=0;int id;int x=1;for(int j=18;j>=0;j--){int id=((k>>j)&1);if(s[x][id]){x=s[x][id];res+=(id<<j);}else{x=s[x][id^1];res+=((id^1)<<j);}}return res;
}
int qmx(int k){//找最小int res=0;int id;int x=1;for(int j=18;j>=0;j--){int id=((k>>j)&1);if(!s[x][id^1]){x=s[x][id];res+=(id<<j);}else{x=s[x][id^1];res+=((id^1)<<j);}}return res;
}
void solve(){for(int i=0;i<=cnt;i++)s[i][0]=s[i][1]=0;cnt=1;int l,r; cin>>l>>r;n=r-l+1;for(int i=1;i<=n;i++){cin>>b[i]; insert(b[i]);}for(int i=1;i<=n;i++){int x=b[i]^l;int mi=qmi(x);int mx=qmx(x);if(mi==(l^x)&&mx==(r^x)){cout<<x<<endl; break;}}
}
CF2041H *2300 DP
\[\begin{flalign*}
&题意:对所有长为n(\leq1e6)的序列,值域[1,k](k\leq1e9)&\\&
求序列对应相邻两数之间的大小关系的方案个数\\&
同时,如果两个序列的相邻两数大小关系一致,视为同一方案\\&
那么题意可以抽象成:符号序列的方案数\\&
思路:考虑由<,>,=组成的符号序列的方案数,n个数的序列被转换成n-1个数的符号序列\\&
由于值域限制,不考虑=的情况下,不能有大于等于连续k个相等的符号出现\\&
所以定义f_i为长为i只由<,>组成的括号序列的方案数\\&
那么转移方程:f_i=\sum_{j=0}^{i-1}[i-j\le k]f_j,如何得到的呢?\\&
定义f_{i,0}为1-i以<结尾的方案数,f_{i,1}为>结尾\\&
那么f_{i,0}=\sum_{j=0}^{i-1}[i-j\le k]f_{j,1},(结尾放连续个<,最多k-1个)\\&
两种符号是对称的,之后*个2就行,特别的,f_0=1\\&
现在考虑答案怎么算,就是求长为i的符号序列中插入=的方案\\&
由隔板法,ans_i=f_i*C(n-1,i)\\&
注意写代码的时候,0个符号的方案是1,前缀和是1\\&
但是统计长度小于k的时候减去的前缀和应该是0\\&
\end{flalign*}
\]
void solve(){cin>>n>>k;vector<int>pre(n+1,0),songs(n+1,0);songs[0]=1;pre[0]=1;for(int i=1;i<n;i++){songs[i]=pre[i-1];if(i-k>=0){songs[i]-=pre[i-k];}songs[i]=(songs[i]+mod)%mod;pre[i]=pre[i-1]+songs[i];pre[i]%=mod;}for(int i=1;i<n;i++) songs[i]*=2,songs[i]%=mod;int ans=0;for(int i=0;i<n;i++){ans+=c(n-1,i)*songs[i]%mod;ans%=mod;}cout<<ans;
}
CF1492D *1900
\[\begin{flalign*}
&题意:给你a个0,b个1,k个1,0 \leq a; 1 \le b; 0 \leq k \leq a + b \leq 2 \cdot 10^5&\\&
要求:构造二进制数x和y,使得二进制x-y中恰好k个1,y\le x,x和y无前导0\\&
首先手玩发现:\\&
结论1: 连续x个1连续y个0-连续y个0连续x个1(x>0;y>0)的结果
有max(x,y)个1\\&
根据结论,可以构造x=11+(a-1)个0+(b-2)个1+1个0\\&
y=1+(a-1)个0+1+1个0+(b-2)个1\\&
可以得到a-1+b-2=a+b-3个1\\&
是否可以继续优化?\\&
发现结论1中贡献连续1最多的部分是x首尾10,y首尾01\\&
进一步x=11s0,y=10s1,s为01串\\&
这样可以得到a+b-2个1,所以最终的构造方案是b个1,a个0连起来,对y串swap\\&
最后考虑特判一种是k>a+b-2不行\\&
上述构造方案要成立当且仅当k\ne0时,有至少两个1,1个0\\&
所以k\ne0的时候 a=1||b=0都不成立\end{flalign*}
\]