做题记录 2026.09
2026.09
CF1615F LEGOndary Grandmaster
看这种对 01 串相邻相同位操作的东西,立刻想到奇偶翻转。于是一次操作相当于移动 1,两个状态可达当且仅当 1 的数量相同,操作次数即对应 1 距离之和。
设 \(f_{i,j},g_{i,j}\) 分别表示起始串当前考虑的最后一个 1 在 \(i\),终止串当前考虑的最后一个 1 在 \(j\) 的答案和方案数即可。
CF1515E Phoenix and Computers
注意到不会有相邻的电脑都是自动打开,所以以自动打开的电脑为分界点,把序列分段,段间独立(多重组合数处理),段内从后往前考虑,最后一个打开的电脑一定是最左侧或最右侧,所以段内方案数 \(2^{len-1}\),dp 即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef __int128_t int128;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
int MOD;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
ll mask;
void init(){
mask=(((int128)1)<<64)/MOD;
}
int mod(ll x){
return x-(((int128)x*mask)>>64)*MOD;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=mod((ll)mul*a);
a=mod((ll)a*a);
b>>=1;
}
return mul;
}
const int N=405;
int pw[N],fact[N],invfact[N];
int n,f[N][N];
void __INIT__(){}
void __SOLVE__(){
scanf("%d%d",&n,&MOD);
init();
pw[0]=1,fact[0]=1;
for(int i=1;i<=n;i++) pw[i]=add2(pw[i-1],pw[i-1]),fact[i]=mod((ll)i*fact[i-1]);
invfact[n]=qpow(fact[n],MOD-2);
for(int i=n;i>=1;i--) invfact[i-1]=mod((ll)i*invfact[i]);
for(int i=1;i<=n;i++) f[i][i]=mod((ll)pw[i-1]*invfact[i]);
for(int i=1;i<=n;i++){
for(int k=1;k<=i-2;k++){
for(int j=1;j<=k;j++) add(f[i][j+i-k-1],mod((ll)f[k][j]*mod((ll)invfact[i-k-1]*pw[i-k-2])));
}
}
int ans=0;
for(int j=1;j<=n;j++) add(ans,mod((ll)f[n][j]*fact[j]));
printf("%d\n",ans);
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
*bonus1: \(\mathcal{O}(n^2)\) dp
类似连续段 dp,按时间顺序从前往后考虑,每次扩展一个段/加入一个段。
具体地,设 \(f_{i,j}\) 表示已经手动打开了 \(i\) 个数(不考虑中间的自动打开),构成了 \(j\) 个连续段的方案数。
转移:
- \((j+1)f_{i,j}\rightarrow f_{i+1,j+1}\)
- \(2jf_{i,j}\rightarrow f_{i+1,j}\)
答案为 \(\sum f_{n-j+1,j}\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef __int128_t int128;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
int MOD;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
ll mask;
void init(){
mask=(((int128)1)<<64)/MOD;
}
int mod(ll x){
return x-(((int128)x*mask)>>64)*MOD;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=mod((ll)mul*a);
a=mod((ll)a*a);
b>>=1;
}
return mul;
}
const int N=405;
int pw[N],fact[N],invfact[N];
int n,f[N][N];
void __INIT__(){}
void __SOLVE__(){
scanf("%d%d",&n,&MOD);
init();
f[0][0]=1;
for(int i=0;i<=n;i++){
for(int j=0;j<=i;j++){
add(f[i+1][j+1],mod((ll)(j+1)*f[i][j]));
add(f[i+1][j],mod(2ll*j*f[i][j]));
}
}
int ans=0;
for(int j=1;j<=n;j++) add(ans,f[n-j+1][j]);
printf("%d\n",ans);
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
*bonus2: 组合数学
枚举段数 \(k\),把 \(n-k+1\) 次手动操作分配到 \(k\) 段中,各段内部方案数之积为 \(2^{n-2k+1}\),则
笔者自研了一个多项式推导,不过比较麻烦而且不够巧妙,不提及。
*CF1442D Sum
又忘了不优性了(在保证答案不变的情况下,适当放宽约束)
设每行分别取了 \(c_i\) 个,发现最优解应满足 \(\forall i\ne j,\ a_{i,c_i+1}\le a_{j,c_j}\),否则可令 \(c_i\leftarrow c_i+1,c_j\leftarrow c_j-1\),取等不好看,所以加一个第二关键字变为 \((a_{i,c_i+1},i)<(a_{j,c_j},j)\)。
注意到 \((a_{i,c_i},i)\le(a_{i,c_i+1},i)<(a_{j,c_j},j)\le(a_{j,c_j+1},j)\) 与 \((a_{j,c_j+1},j)<(a_{i,c_i},i)\) 矛盾,故最多一个 \(i\) 满足 \(0<c_i<t_i\),记为 \(i_0\)。
此时笔者想更进一步,注意到被取完的行最后一个数 \(>a_{i,c_{i_0}+1}\),但这导致背包的元素特别乱。所以适当放宽,对 \(\{1,\dots,n\}\setminus\{i\}\) 跑背包,直接分治计算即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=3005;
int n,k,t[N];
vector<int> a[N];
pil e[N];
ll ans=-infll;
void solve(int l,int r,vector<ll> &f){
if(l==r){
ll sum=0;
for(int i=0;i<=t[l]&&i<=k;i++){
sum+=a[l][i];
chkmax(ans,f[k-i]+sum);
}
return;
}
vector<ll> f0=f;
int mid=(l+r)>>1;
for(int i=mid+1;i<=r;i++) for(int j=k;j>=e[i].first;j--) chkmax(f[j],f[j-e[i].first]+e[i].second);
solve(l,mid,f);
f.swap(f0);
for(int i=l;i<=mid;i++) for(int j=k;j>=e[i].first;j--) chkmax(f[j],f[j-e[i].first]+e[i].second);
solve(mid+1,r,f);
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&t[i]);
a[i].resize(t[i]+1);
ll sum=0;
for(int j=1;j<=t[i];j++) scanf("%d",&a[i][j]),sum+=a[i][j];
e[i]={t[i],sum};
}
vector<ll> f(k+1,-infll);
f[0]=0;
solve(1,n,f);
printf("%lld\n",ans);
return 0;
}
CF1603C Extreme Extension
先考虑计算 \(f(1,n)\),从右往左贪心,显然每个数被拆成越大越好(因为数量少,且后续发展空间高),设上一个数留下了 \(x\) 的限制,则(手玩特殊情况可知)当前 \(a_i\) 应拆成共 \(\left\lceil\frac{a_i}x\right\rceil\) 个 \(\left\lfloor\frac{a_i}{\left\lceil\frac{a_i}x\right\rceil}\right\rfloor,\left\lceil\frac{a_i}{\left\lceil\frac{a_i}x\right\rceil}\right\rceil\)。
这个贡献不太好化简,于是不化简了,而注意到 \(x\) 是 \(a_{i+1}\) 的整除,所以只有 \(\mathcal{O}(\sqrt{a_{i+1}})\) 种,开个桶分别对每种 \(x\) 维护对应子区间右端点个数,复杂度 \(\mathcal{O}(n\sqrt{V})\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=100005;
int n,a[N];
int cnt[N],ucnt[N];
void __INIT__(){}
void __SOLVE__(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int ans=0;
for(int i=1;i<=a[n];i++) cnt[i]=0;
add(cnt[a[n]],1);
for(int i=n-1;i>=1;i--){
for(int l=1,r;l<=a[i];l=r+1){
r=a[i]/(a[i]/l);
ucnt[a[i]/l]=0;
}
for(int l=1,r;l<=a[i+1];l=r+1){
r=a[i+1]/(a[i+1]/l);
int v=a[i+1]/l,c=cnt[v];
int t=(a[i]+v-1)/v;
add(ans,(ll)(t-1)*c*i%MOD);
add(ucnt[a[i]/t],c);
}
add(ucnt[a[i]],1);
for(int l=1,r;l<=a[i];l=r+1){
r=a[i]/(a[i]/l);
// printf("(%d,%d) ",a[i]/l,ucnt[a[i]/l]);
cnt[a[i]/l]=ucnt[a[i]/l];
}
// printf("\n");
}
printf("%d\n",ans);
}
int main(){
int T=1;
scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
[Sqrt5 Round 7] 道路替换 加强版
idea & sol by: ChenYuXiang

这个最短路问题是 luogu P3084 的差分约束形式。所以单看 \(s\) 左侧和右侧都是好做的,注意最短路是满足约束的最大解,而右侧为正,左侧为负,所以右侧是最大化问题,左侧是最小化问题。又因为有最短路形式,所以可使所有点同时达到最优,在保证最大/最小化前提下 尽量往左放即可。问题是跨 \(s\) 约束。
依旧考虑存在全局最优解,所以对于右侧,只需在保证左侧有解的情况下最大化即可,左侧同理。所以在正常跑从 \(l\) 到 \(r\) dp 的基础上,跨过 \(s\) 时,把左侧的值改为 \(\{-\infty,0\}\),求方案时,先把右侧方案求出,左侧在能转移到右侧方案的位置中选。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef unsigned long long ull;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=500005;
int n,m,f[N],op[N],lim[N],res[N];
pii all[N],pos[N];
vector<pii> seg;
deque<pii> q;
template<typename Cmp>
void pb(pii p,Cmp fn){
while(q.size()&&fn(q.back().second,p.second)) q.pop_back();
q.push_back(p);
}
void pf(int L){while(q.size()&&q.front().first<L) q.pop_front();}
void solve(int l,int r,int s){
lim[r+1]=r+1;
for(int i=r,p=seg.size()-1;i>=l;i--){
lim[i]=i;
if(i<r) chkmin(lim[i],lim[i+1]);
for(;p>=0&&seg[p].second==i;p--) chkmin(lim[i],seg[p].first);
}
int L=l-1,R=l,p=0;// [L,R)
f[l-1]=0;
q.clear();
q.push_back({l-1,0});
for(int i=l;i<=r+1;i++){
for(;R<lim[i];R++){
if(i<=s) pb({R,f[R]},greater<int>());
else if(R<=s){
if(f[R]>=inf) pb({R,-inf},less<int>());
else pb({R,0},less<int>());
}
else pb({R,f[R]},less<int>());
}
pos[i]={L,R};
if(L>=R){
if(i<=s) f[i]=inf;
else f[i]=-inf;
}
else{
pf(L);
op[i]=q.front().first,f[i]=q.front().second+1;
}
if(i==s){
q.clear();
for(int i=L;i<R;i++){
if(i<=s){
if(f[i]>=inf) pb({i,-inf},less<int>());
else pb({i,0},less<int>());
}
else pb({i,f[i]},less<int>());
}
}
for(;p<seg.size()&&seg[p].second==i;p++) chkmax(L,seg[p].first);
}
if(f[r+1]<0){
printf("-1\n");
return;
}
for(int i=l;i<=r;i++) res[i]=0;
p=r+1;
while(op[p]>s) res[op[p]]=1,p=op[p];
pii mn{inf,inf};
for(int i=pos[p].first;i<pos[p].second&&i<=s;i++) chkmin(mn,{f[i],i});
p=mn.second;
while(p>=l) res[p]=1,p=op[p];
for(int i=l;i<=r;i++) res[i]+=res[i-1];
// for(int i=l;i<=r;i++) printf("%d ",res[i]);printf("\n");
ull ans=0;
for(int i=l;i<=r;i++) ans^=(ull)i*(res[i]-res[s]);
printf("%llu\n",ans);
}
int main(){
#ifndef JZQ
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
#endif
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d%d",&all[i].first,&all[i].second),all[i].first++;
sort(all+1,all+m+1,[&](pii x,pii y){return x.second<y.second;});
int q;
scanf("%d",&q);
while(q--){
int l,r,s;
scanf("%d%d%d",&l,&r,&s);
seg.clear();
for(int i=1;i<=m;i++) if(l<all[i].first&&all[i].second<=r) seg.push_back(all[i]);
solve(l,r,s);
}
return 0;
}
*CF1740F Conditional Mix
好难的观察性质。
把最终集合从大到小排序,一个集合为一列,第 \(i\) 个集合大小 \(s_i\)。不难想到加 \(0\) 补全为 \(n\) 列。
最基本的 case:第 \(i\) 个数放到前 \(cnt_i\) 列中。
从这个 case 开始调整。对于两列 \(i,j\),若 \(s_i>s_j\),则 \(i\) 中存在 \(j\) 中没有的元素,可将 \(s_i\leftarrow s_i-1,s_j\leftarrow s_j+1\)。故只要满足:
- \(\forall 1\le i\le n,\ \sum\limits_{j=1}^is_j\le\sum\limits_{v=1}^n\min(cnt_v,i)\);
- \(\forall 1\le i<n,\ s_i\ge s_{i+1}\)。
就可被调整得到。
而这也是合法的必要条件,若不满足 1. 则肯定存在某一列中有重复元素。
设 dp \(f_{i,j,k}\) 表示考虑了前 \(i\) 列,和为 \(j\),\(s_i=k\) 的方案数,转移:
维护 \(k\) 的后缀和即可,代码中只维护了后缀和,未维护原数组。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=2005;
int n,a[N],cnt[N],sum[N],f[2][N][N];
void __INIT__(){}
void __SOLVE__(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),cnt[a[i]]++;
for(int i=1;i<=n;i++) sum[1]++,sum[cnt[i]+1]--;
for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
for(int k=0;k<=n;k++) f[0][0][k]=1;
for(int i=1;i<=n;i++){
int t=i&1;
for(int j=0;j<=n;j++) for(int k=0;k<=n/i;k++) f[t][j][k]=0;
for(int j=0;j<=sum[i];j++){
for(int k=0;k<=j/i;k++) f[t][j][k]=f[t^1][j-k][k];
for(int k=j/i-1;k>=0;k--) add(f[t][j][k],f[t][j][k+1]);
}
}
printf("%d\n",f[n&1][n][0]);
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
QOJ18984 [NOI2026 D1T2] 传送
ER: 80 S: 80
通行方式形如:把原图划分为若干连通块,每个连通块为内向树,根处进行传送或结束,其他结点走 \(fa\)。
\(x\) 到 \(y\) 不经过传送是好做的,只考虑经过传送的情况,先考虑第一次传送后的期望步数 \(E_1\)。
设\(y\) 所在连通块大小 \(s\),每个点到对应根距离 \(w_i\),则
在 \(s\) 不变的情况下,应最小化 \(\sum w_i\),则 其他连通块都应为孤立点,易知这同时能使第一次传送前的步数最小化。
接下来记 \(y\) 所在连通块(后省略“\(y\) 所在”)内所有点到 \(y\) 距离和为 \(w\),则总期望步数
考虑连通块内结构,首先中间的层都应用满,其次假设最后一层没有用满,而用了 \(c\) 个点,到根距离 \(d\),令 \(s,w\) 不含该层,则
存在一个固定的调整方向,在 \(c\) 达到极值时 \(E\) 最小化。故 连通块内每层都应填满。
打表发现随着半径 \(r\) 的增加,\(E\) 呈现单谷(后面会证明),于是三分/二分斜率+邻域查询(点分树)可做到 \(\mathcal{O}(n\log^2n)\)(\(E\) 只与 \(y\) 有关,故只需求 \(n\) 次而非 \(m\) 次)
(以上是赛时的想法)
二分很难,尝试直接观察 \(E\) 的变化。
由于前面的单谷结论(还没到证明的时候),最优的 \(r\) 为第一个满足 \(s(r+1)-w\ge n\) 的位置。
考虑类似换根 DP,增量维护。绕过第一个假做法,发现还是不好直接维护,于是再做一次差分,尝试找出一些性质:
所以 \(s(r+1)-w\) 单增,所以 \([s(r+1)-w\ge n]\) 单增,所以 \([\Delta E\ge 0]\) 单增,单谷得证。另外,到这一步可以看出 \(\mathcal{O}(n\sqrt{n})\) 的做法,但笔者认为这不够快,所以进行了进一步思考。
然后笔者得到了一个长剖+线段树的假做法。仍考虑换根,注意到保持半径不变换根重叠率很高。若 \(r'=r+1\),带入差分可知 \(E'\ge E\ge n\);若 \(r'=r-2\),类似地可知 \(E'\le E_{pre}<n\)。故:
所以换根时,每次 \(r\) 变化 \(\mathcal{O}(1)\),直接判一下即可。
#include"teleport.h"
#include<bits/stdc++.h>
namespace SmpaelFx{
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
ll gcd(ll a,ll b){
if(!b) return a;
return gcd(b,a%b);
}
bool frac_less(pli x,pli y){return (__int128_t)x.first*y.second<(__int128_t)y.first*x.second;}
const int N=500005;
int n,m;
vector<int> G[N];
int fa[N][25],dep[N];
void dfs(int u,int f){
fa[u][0]=f,dep[u]=dep[f]+1;
for(auto v:G[u]){
if(v==f) continue;
dfs(v,u);
}
}
void lca_init(){
dfs(0,0);
for(int l=1;l<=20;l++) for(int i=1;i<=n;i++) fa[i][l]=fa[fa[i][l-1]][l-1];
}
int lca(int u,int v){
if(dep[u]>dep[v]) swap(u,v);
for(int i=20;i>=0;i--) if(dep[fa[v][i]]>=dep[u]) v=fa[v][i];
if(u==v) return u;
for(int i=20;i>=0;i--) if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int dis(int u,int v){return dep[u]+dep[v]-2*dep[lca(u,v)];}
pli E[N];
int vis[N],sz[N],tot,id[N],fat[N],dept[N];
vector<vector<pil>> value[N];
vector<int> dist[N];
pii findRt(int u,int f,int n){
sz[u]=1;
int mx=0;
pii mn={inf,-1};
for(auto v:G[u]){
if(v==f||vis[v]) continue;
chkmin(mn,findRt(v,u,n));
sz[u]+=sz[v];
chkmax(mx,sz[v]);
}
return min(mn,{max(mx,n-sz[u]),u});
}
void calcVal(int u,int f,int rt,int idx,int dep){
sz[u]=1,dist[u].push_back(dep);
while(value[rt][0].size()<=dep) value[rt][0].push_back({0,0});
value[rt][0][dep].first++,value[rt][0][dep].second+=dep;
if(idx){
while(value[rt][idx].size()<=dep) value[rt][idx].push_back({0,0});
value[rt][idx][dep].first++,value[rt][idx][dep].second+=dep;
}
for(auto v:G[u]){
if(v==f||vis[v]) continue;
if(!idx) value[rt].push_back(vector<pil>()),calcVal(v,u,rt,++tot,dep+1);
else calcVal(v,u,rt,idx,dep+1);
sz[u]+=sz[v];
}
}
int build(int rt,int n,int d){
rt=findRt(rt,rt,n).second;
tot=0,value[rt].push_back(vector<pil>());
calcVal(rt,rt,rt,0,0);
vis[rt]=1,dept[rt]=d;
for(auto &v:value[rt])
for(int i=1;i<v.size();i++) v[i].first+=v[i-1].first,v[i].second+=v[i-1].second;
// printf("build %d\n",rt);
// for(auto &v:value[rt]){
// for(auto [s,w]:v) printf("(%d,%d) ",s,w);
// printf("\n");
// }
int tot=0;
for(auto v:G[rt]){
if(vis[v]) continue;
int nv=build(v,sz[v],d+1);
id[nv]=++tot,fat[nv]=rt;
}
return rt;
}
template<typename T>
inline T visit(const vector<T> &vec,int i){return vec[min(i,(int)vec.size()-1)];}
pil query(int u,int r){
int u0=u;
int s=0;ll w=0;
for(int lst=u;u!=-1;lst=u,u=fat[u]){
int d=dist[u0][dept[u]];
if(r<d) continue;
auto [ds,dw]=visit(value[u][0],r-d);
s+=ds,w+=(ll)ds*d+dw;
if(lst!=u){
auto [nds,ndw]=visit(value[u][id[lst]],r-d);
s-=nds,w-=(ll)nds*d+ndw;
}
}
return {s,w};
}
ll calc(int u,int r){
// printf("calc %d %d\n",u,r);
auto [s,w]=query(u,r);
// printf("%d %lld %lld\n",s,w,(ll)s*(r+1)-w);
return (ll)s*(r+1)-w;
}
void calcE(int u,int f,int r){
if(calc(u,r)<n) for(r++;calc(u,r)<n;r++);
else for(;r>=0&&calc(u,r-1)>=n;r--);
auto [s,w]=query(u,r);
ll g=gcd(s,w+n);
E[u]={(w+n)/g,s/g};
for(auto v:G[u]){
if(v==f) continue;
calcE(v,u,r);
}
}
vector<pli> teleport(vector<int> u,vector<int> v,vector<int> x,vector<int> y){
for(int i=0;i<n-1;i++) G[u[i]].push_back(v[i]),G[v[i]].push_back(u[i]);
lca_init();
fat[build(0,n,0)]=-1;
calcE(0,0,0);
vector<pli> res;
for(int i=0;i<m;i++){
pli ans=min({dis(x[i],y[i]),1},E[y[i]],frac_less);
// printf("(%d,%d) vs (%d,%d)\n",dis(x[i],y[i]),1,E[y[i]].first,E[y[i]].second);
// printf("ans=(%d,%d)\n",ans.first,ans.second);
res.push_back(ans);
}
return res;
}
}
std::vector<std::pair<long long, int>> teleport(int c, int n, int m, std::vector<int> u, std::vector<int> v, std::vector<int> x, std::vector<int> y){
SmpaelFx::n=n,SmpaelFx::m=m;
return SmpaelFx::teleport(u,v,x,y);
}
QOJ18986 [NOI2026 D2T1] 中位数
ER: 30 S: 100
看到最大化中位数,立刻想到二分,于是尝试线性做 01 序列。
场上想在保证段数的前提下最大化 \(1\) 段数,于是寄了
只需要划分中有 \(\left\lceil\frac{k}2\right\rceil\) 个中位数为 \(1\) 的段。任取这么多个可能会超限,但注意到对于 \(k\) 奇,最多也只会超 \(1\) 段,只需要存在两个 \(1\) 段相邻(或与边界相邻)即可;对于 \(k\) 偶,只会超 \(2\) 段,只需要存在 \(2\) 组相邻即可。
大分讨,特判 \(k\le 5\)(因为上面的结构可能需要过多的 \(1\) 段)。
#include "median.h"
#include<bits/stdc++.h>
namespace SmpaelFx{
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=1000005;
int n,k,lst[N<<1],pre[N],pre2[N],f[N][3],g[N][15],mx[15];
int a[N],b[N],cnt[N];
template<typename T>
T DEBUG(T x){printf("%d\n",x);return x;}
#define RETURN(x) {return (x);}
bool check(int mid){
for(int i=0;i<=(n<<1);i++) lst[i]=-1;
int c=n;
lst[c]=0,pre[0]=0;
for(int i=0;i<=n;i++) for(int j=0;j<3;j++) f[i][j]=-inf;
f[0][0]=0;
for(int i=1;i<=n;i++){
b[i]=(a[i]>=mid);
if(b[i]) c++;
else c--;
cnt[i]=c-n;
pre[i]=pre2[i]=-1;
if(b[i]) pre[i]=i-1;
if(lst[c]!=-1){
if(pre[i]==-1) pre[i]=lst[c];
else pre2[i]=lst[c];
}
lst[c]=i;
for(int j=0;j<3;j++){
f[i][j]=b[i]+f[i-1][j];
if(i==n&&pre[i]>=0&&j) chkmax(f[i][j],f[pre[i]][j-1]+1);
}
if(pre[i]>=0&&pre[pre[i]]>=0){
for(int j=1;j<3;j++) chkmax(f[i][j],f[pre[pre[i]]][j-1]+1+(!!pre[i]));
if(i==n) chkmax(f[i][2],f[pre[pre[i]]][0]+1+(!!pre[i]));
}
if(pre2[i]>=0&&pre[pre2[i]]>=0){
for(int j=1;j<3;j++) chkmax(f[i][j],f[pre[pre2[i]]][j-1]+1+(!!pre2[i]));
if(i==n) chkmax(f[i][2],f[pre[pre2[i]]][0]+1+(!!pre2[i]));
}
if(pre[i]>0&&pre[pre[i]]>=0&&pre[pre[pre[i]]]>=0) chkmax(f[i][2],f[pre[pre[pre[i]]]][0]+2+(!!pre[pre[i]]));
if(pre2[i]>0&&pre[pre2[i]]>=0&&pre[pre[pre2[i]]]>=0) chkmax(f[i][2],f[pre[pre[pre2[i]]]][0]+2+(!!pre[pre2[i]]));
if(pre[i]>0&&pre2[pre[i]]>=0&&pre[pre2[pre[i]]]>=0) chkmax(f[i][2],f[pre[pre2[pre[i]]]][0]+2+(!!pre2[pre[i]]));
if(pre2[i]>0&&pre2[pre2[i]]>=0&&pre[pre2[pre2[i]]]>=0) chkmax(f[i][2],f[pre[pre2[pre2[i]]]][0]+2+(!!pre2[pre2[i]]));
}
if(k==2||k==3||k==5){
mx[0]=0;
for(int j=1;j<=k;j++) mx[j]=-inf;
for(int i=1;i<=n;i++){
g[i][0]=-inf;
for(int j=k;j>=1;j--){
g[i][j]=mx[j-1];
if(pre[i]>=0) chkmax(g[i][j],g[pre[i]][j-1]+1);
if(pre2[i]>=0) chkmax(g[i][j],g[pre2[i]][j-1]+1);
chkmax(mx[j],g[i][j]);
}
}
return g[n][k]>=(k+1)/2;
}
if(k&1) RETURN(f[n][2]>=(k+1)/2);
RETURN(f[n][1]>=(k+1)/2);
}
void init(int c,int t){}
int median(){
int l=1,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
return l;
}
}
void init(int c, int t){
SmpaelFx::init(c,t);
}
int median(int n, int k, std::vector<int> a){
SmpaelFx::n=n,SmpaelFx::k=k;
std::copy(a.begin(),a.end(),SmpaelFx::a+1);
return SmpaelFx::median();
}
QOJ18988. [NOI2026 D2T3] 彩虹树
ERS: 40
发现无论是枚举 \(S\) 计数 \(c\) 还是反过来都不好做,尝试同时计数 \(S,c\)。
于是想到设 \(f_{u,i,j}\) 表示 \(u\) 子树,\(c_u=i\),有 \(j\) 种颜色待匹配(有祖先颜色为他)。\(j\) 是用来贪心判定的,所以每个 \((S,c)\) 应该贡献到对应可能的最小的 \(j\) 处。
发现为了避免重复,不能逐个添加子树,而是要直接从子结点颜色数贡献到 \(u\)。
不妨先做二叉树,此时没有上面重复的问题。设两个儿子的状态分别是 \((i_1,j_1),(i_2,j_2)\),颜色交集大小 \(x\),则 \((i',j')=(i_1+i_2-x,j_1+j_2-\min\{j_1,j_2,x\})\)。发现这些点构成一条横线+一条斜线,用两个差分数组分别维护即可。
(以上是赛时做法,我声称后面没想到是时间不够了)
猜测对于多棵子树,也可以写成类似的形式,尝试合并 \((i',j')\) 与 \((i_3,j_3)\),可得一个两个变量的式子。打表发现对固定的 \(i''\),只取最小的 \(j''\),则满足 \(j''=\max\{j_1,j_2,j_3,i''-(i_1+i_2+i_3)+(j_1+j_2+j_3)\}\)。猜测 \(j'=\max\{\max j,i'-\sum i+\sum j\}\)。
???+ (可能不太严谨的)证明
对两棵子树的情况,写成 \(j'=\max\{j_1,j_2,i'-(i_1+i_2)+(j_1+j_2)\}\)。
三棵子树,有
因为不应该与子树添加顺序有关,去掉一些非对称式可得到猜测结果。
???-
于是设一个中间状态 \(g_{a,b,c,d}\) 分别记录 \(\max i,\sum i,\max j,\sum j\),最后用刚才的差分转移可以做到 \(\mathcal{O}(n^6)\)。
感觉记得东西太多了,而且差分不一定要同时做,写出差分的三个“关键点”的式子:
发现分别可以用较少的参数维护,于是分开转移,设:
- \(g1_{a,c}\) 记录 \(\max i,\max j\)
- \(g2_{b,c}\) 记录 \(\sum(i-j),\max j\)
- \(g3_{b,d}\) 记录 \(\sum i,\sum j\)
复杂度 \(\mathcal{O}(n^4)\)。
#include"rainbow.h"
#include<bits/stdc++.h>
namespace SmpaelFx{
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=205;
int n,fa[N],sz[N];
vector<int> G[N];
int f[N][N][N],g[N][N],h[N][N],d1[N][N],d2[N][N];
void dfs(int u){
sz[u]=1;
int mx=1;
for(auto v:G[u]){
dfs(v);
chkmax(mx,sz[v]);
sz[u]+=sz[v];
}
for(int i=0;i<=sz[u];i++) for(int j=0;j<=i;j++) d1[i][j]=d2[i][j]=0;
// pt1: (max(mi,1),max(mj-1,0))
sz[u]=1,mx=1;
for(int a=0;a<=mx;a++) for(int c=0;c<=a;c++) g[a][c]=0;
g[0][0]=1;
for(auto v:G[u]){
for(int a=0;a<=max(mx,sz[v]);a++) for(int c=0;c<=a;c++) h[a][c]=0;
for(int a=0;a<=mx;a++) for(int i=0;i<=sz[v];i++) for(int c=0;c<=a;c++) for(int j=0;j<=i;j++)
add(h[max(a,i)][max(c,j)],(ll)g[a][c]*f[v][i][j]%MOD);
for(int a=0;a<=max(mx,sz[v]);a++) for(int c=0;c<=a;c++) g[a][c]=h[a][c];
sz[u]+=sz[v],chkmax(mx,sz[v]);
}
for(int a=0;a<=mx;a++) for(int c=0;c<=a;c++) add(d1[max(a,1)][max(c-1,0)],g[a][c]);
// pt2: ((si+1)-sj+max(mj-1,0),max(mj-1,0))
sz[u]=1,mx=1;
for(int b=0;b<=sz[u];b++) for(int c=0;c<=mx;c++) g[b][c]=0;
g[0][0]=1;
for(auto v:G[u]){
for(int b=0;b<=sz[u]+sz[v];b++) for(int c=0;c<=max(mx,sz[v]);c++) h[b][c]=0;
for(int b=0;b<=sz[u];b++) for(int i=0;i<=sz[v];i++) for(int c=0;c<=mx;c++) for(int j=0;j<=i;j++)
add(h[b+i-j][max(c,j)],(ll)g[b][c]*f[v][i][j]%MOD);
for(int b=0;b<=sz[u]+sz[v];b++) for(int c=0;c<=max(mx,sz[v]);c++) g[b][c]=h[b][c];
sz[u]+=sz[v],chkmax(mx,sz[v]);
}
for(int b=0;b<=sz[u];b++) for(int c=0;c<=mx;c++){
add(d1[b+1+max(c-1,0)][max(c-1,0)],MOD-g[b][c]);
add(d2[b+1+max(c-1,0)][max(c-1,0)],g[b][c]);
}
// pt3: ((si+1)+1,sj+1)
sz[u]=1,mx=1;
for(int b=0;b<=sz[u];b++) for(int d=0;d<=b;d++) g[b][d]=0;
g[0][0]=1;
for(auto v:G[u]){
for(int b=0;b<=sz[u]+sz[v];b++) for(int d=0;d<=b;d++) h[b][d]=0;
for(int b=0;b<=sz[u];b++) for(int i=0;i<=sz[v];i++) for(int d=0;d<=b;d++) for(int j=0;j<=i;j++)
add(h[b+i][d+j],(ll)g[b][d]*f[v][i][j]%MOD);
for(int b=0;b<=sz[u]+sz[v];b++) for(int d=0;d<=b;d++) g[b][d]=h[b][d];
sz[u]+=sz[v],chkmax(mx,sz[v]);
}
for(int b=0;b<=sz[u];b++) for(int d=0;d<=b;d++) add(d2[b+2][d+1],MOD-g[b][d]);
// calc d,f
for(int i=1;i<=sz[u];i++) for(int j=0;j<=i;j++) add(d1[i][j],d1[i-1][j]);
for(int i=1;i<=sz[u];i++) for(int j=1;j<=i;j++) add(d2[i][j],d2[i-1][j-1]);
for(int i=0;i<=sz[u];i++) for(int j=i;j>=0;j--) f[u][i][j]=add2(d1[i][j],d2[i][j]),add(f[u][i][j+1],f[u][i][j]);
}
int rainbow(const vector<int> &fat){
for(int i=2;i<=n;i++){
fa[i]=fat[i-1]+1;
G[fa[i]].push_back(i);
}
dfs(1);
int ans=0;
for(int i=1;i<=n;i++) add(ans,f[1][i][0]);
return ans;
}
}
int rainbow(int c, int n, std::vector<int> f){
SmpaelFx::n=n;
return SmpaelFx::rainbow(f);
}
*bonus:
\(\max\) 卷积可以前缀和优化,瓶颈在 \(g3\)。
直接 ntt 可以做到 \(\mathcal{O}(n^3\log n)\)。
事实上,对于此类二维树上背包,可以参考这个文章做到 \(\mathcal{O}(n^3)\)。
QOJ18987 [NOI2026 D2T2] 木棉
ERS: 20
\(x,y\) 连边分三类:
- \(x\) 连向 \(y\);
- \(y\) 连向 \(x\);
- \(x,y\) 是最后留下来的两个点。
发现只要对 \(x\)(和 \(y\))知道何时弹出即可。
以计算 \(y\) 为例。维护 \(pos_i\) 表示还原 \(prufer\) 序列过程中,做完第 \(i\) 个数后,\(y\) 在堆中有 \(pos_i\) 个比它小的。记 \(lst_i\) 为 \(i\) 在 \([l,r)\) 中最后一次出现,转移为:
弹出时刻即 \(lst_y\) 后第一个零点位置 \(+1\)。
首先有 \(lst\),不难想到扫描 \(r\);有 \(\max,+\),想到观察折线。发现折线为:从 \(\sum[lst_i<l]\) 开始,每次 \(-1\) 或不变,碰到 \(0\) 后,沿 \(0\) 走一段,再沿 \(1\) 走一段,以此交替。
记 \(d_i=1-[lst_{a_i}=i\land a_i<y]\)。
则零点有两类:
- 第一个零点,为最小的 \(p\) 满足 \(\sum\limits_{i=l}^{p}d_i=\sum[lst_i<l]\) 可以通过求所有 \(d_i=1\) 位置的
kth计算; - 其余零点,当且仅当 \(d_i=1\),有效的是 \(lst_y\) 后第一个。
发现 \(d_i\) 与 \(l\) 无关,于是扫 \(r\),则变为在二维范围内求 kth 和 cnt,拿 线段树套BIT + 线段树上二分 维护一下即可,复杂度 \(\mathcal{O}((n+q)\log^2n)\)。
*卡常
感谢 GeMini 3.1 Pro。
要点一:内层信息均为线性,所以可以在全局开一个内存池,而非每个节点开 vector。
要点二:内层需要离散化,查询时需要 lower_bound,可以类似分散层叠,对线段树区间 \([l,r]\),build 归并时维护每个数在 \([l,mid],[mid+1,r]\) 中的 lower_bound,这样查询只需要在根节点 lower_bound 一次。
#include "kapok.h"
#include<bits/stdc++.h>
namespace SmpaelFx{
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=200005;
int n,a[N],lst[N];
struct Query{
int l,r,x,y,id;
} qs[N];
bool cmp(Query x,Query y){return x.r<y.r;}
inline int lowbit(int x){return x&(-x);}
int num_pool[N<<6],bit_pool[N<<6],to_l_pool[N<<6],to_r_pool[N<<6],cur=0;
struct Node{
int n;
int *num,*bit,*to_l,*to_r;
void add(int x,int v){for(x++;x<=n;x+=lowbit(x)) bit[x]+=v;}
int query(int x){
int s=0;
for(;x;x-=lowbit(x)) s+=bit[x];
return s;
}
} tr[N<<2];
void build(int x,int l,int r){
if(l==r){
vector<int> num;
if(l) num.push_back(a[l]);
else for(int j=0;j<n+2;j++) num.push_back(j);
tr[x].num=num_pool+cur,tr[x].bit=bit_pool+cur,tr[x].to_l=to_l_pool+cur,tr[x].to_r=to_r_pool+cur;
tr[x].n=num.size(),cur+=num.size()+1;
copy(num.begin(),num.end(),tr[x].num);
return;
}
int mid=(l+r)>>1;
build(x<<1,l,mid),build(x<<1|1,mid+1,r);
tr[x].num=num_pool+cur,tr[x].bit=bit_pool+cur,tr[x].to_l=to_l_pool+cur,tr[x].to_r=to_r_pool+cur;
int i=0,j=0,k=0;
while(i<tr[x<<1].n&&j<tr[x<<1|1].n){
tr[x].to_l[k]=i,tr[x].to_r[k]=j;
if(tr[x<<1].num[i]==tr[x<<1|1].num[j]){
tr[x].num[k++]=tr[x<<1].num[i];
i++,j++;
}
else if(tr[x<<1].num[i]<tr[x<<1|1].num[j]) tr[x].num[k++]=tr[x<<1].num[i++];
else tr[x].num[k++]=tr[x<<1|1].num[j++];
}
while(i<tr[x<<1].n) tr[x].to_l[k]=i,tr[x].to_r[k]=j,tr[x].num[k++]=tr[x<<1].num[i++];
while(j<tr[x<<1|1].n) tr[x].to_l[k]=i,tr[x].to_r[k]=j,tr[x].num[k++]=tr[x<<1|1].num[j++];
tr[x].to_l[k]=i,tr[x].to_r[k]=j;
tr[x].n=k,cur+=tr[x].n+1;
}
void insert(int x,int l,int r,int p,int v){
if(x==1) v=lower_bound(tr[x].num,tr[x].num+tr[x].n,v)-tr[x].num;
tr[x].add(v,1);
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) insert(x<<1,l,mid,p,tr[x].to_l[v]);
else insert(x<<1|1,mid+1,r,p,tr[x].to_r[v]);
}
void move(int x,int l,int r,int ql,int qr,int v){
if(x==1) v=lower_bound(tr[x].num,tr[x].num+tr[x].n,v)-tr[x].num;
if(ql<l&&qr>r) return;
if(ql<l) tr[x].add(v,1);
if(qr>r) tr[x].add(v,-1);
if(l==r) return;
int mid=(l+r)>>1;
if(ql<=mid) move(x<<1,l,mid,ql,qr,tr[x].to_l[v]);
if(qr>mid) move(x<<1|1,mid+1,r,ql,qr,tr[x].to_r[v]);
}
// [ql,qr]*[-inf,v)
int query(int x,int l,int r,int ql,int qr,int v){
if(x==1) v=lower_bound(tr[x].num,tr[x].num+tr[x].n,v)-tr[x].num;
if(ql<=l&&r<=qr) return tr[x].query(v);
int mid=(l+r)>>1,s=0;
if(ql<=mid) s+=query(x<<1,l,mid,ql,qr,tr[x].to_l[v]);
if(qr>mid) s+=query(x<<1|1,mid+1,r,ql,qr,tr[x].to_r[v]);
return s;
}
int find(int x,int l,int r,int ql,int qr,int v,int &k){
if(x==1) v=lower_bound(tr[x].num,tr[x].num+tr[x].n,v)-tr[x].num;
if(ql<=l&&r<=qr){
int s=(r-l+1)-tr[x].query(v);
if(s<k){
k-=s;
return -1;
}
}
if(l==r) return l;
int mid=(l+r)>>1;
if(ql<=mid){
int ret=find(x<<1,l,mid,ql,qr,tr[x].to_l[v],k);
if(ret!=-1) return ret;
}
return find(x<<1|1,mid+1,r,ql,qr,tr[x].to_r[v],k);
}
int calc(int l,int r,int v){
if(v==r-l+2) return r+1;
int d=query(1,0,n,0,l-1,v);
if(!d&&lst[v]<l) return l-1;
int k;
int t1=find(1,0,n,max(lst[v],l),r,v,k=1);
int t2=l-1;
if(d) t2=find(1,0,n,l,r,v,d);
return max(t1,t2);
}
vector<bool> kapok(int m){
sort(qs,qs+m,cmp);
vector<bool> ans(m);
build(1,0,n);
for(int i=0;i<n+2;i++) insert(1,0,n,0,i);
int j=0;
for(;j<m&&qs[j].r==0;j++) ans[qs[j].id]=1;
for(int r=1;r<=n;r++){
move(1,0,n,lst[a[r]],r,a[r]);
lst[a[r]]=r;
for(;j<m&&qs[j].r==r;j++){
int l=qs[j].l,x=qs[j].x,y=qs[j].y;
if(l>r){
ans[qs[j].id]=1;
continue;
}
int tx=calc(l,r,x),ty=calc(l,r,y);
ans[qs[j].id]=((tx<r&&min(a[tx+1],r-l+2)==y)||(ty<r&&min(a[ty+1],r-l+2)==x)||(tx>=r&&ty>=r));
}
}
for(int j=0;j<m;j++) if(qs[j].x==qs[j].y) ans[qs[j].id]=0;
return ans;
}
}
std::vector<bool> kapok(int c, int n, int m, std::vector<int> a,
std::vector<int> l, std::vector<int> r,
std::vector<int> x, std::vector<int> y) {
SmpaelFx::n=n;
std::copy(a.begin(),a.end(),SmpaelFx::a+1);
for(int i=0;i<m;i++) SmpaelFx::qs[i]={l[i]+1,r[i],x[i],y[i],i};
return SmpaelFx::kapok(m);
}
*优雅做法
注意到 \(d_{lst_y}=1\),所以第二类零点的查询结果一定是 \(lst_y\)。
第一类 \(\sum[lst_i<l]\) 可以预处理。
这样 \(\mathcal{O}(6q\log^2n)\) 直接变为 \(\mathcal{O}(2q\log^2n)\)。
*QOJ18985 [NOI26 D1T3] 布丁
E: 17 R: 16 S: ~30
类似 wordle,找一个比较优的决策树。
每次让新的最大等价类最小。随机+爬山,注意应不均匀随机,尽量取(可重)质因数个数更多的。
使用了 GeMini 3.1 Pro agent 进行调参。
关于卡常:预处理 gcd(如果你知道一个东西较不取模 gcd,应该也可以),用双指针代替 lower_bound。
卡常前:
#include"pudding.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
int gcd(int a,int b){
if(!b) return a;
return gcd(b,a%b);
}
const int T=2000,T2=100,MaxFailcnt=20,lmin=5,lmax=5;
mt19937 rnd(19491001);
int query_tastiness(std::vector<int> a);
pii number[4505];
int myrand(){
int x=rnd()%(4500*4500),rt=sqrt(x);
while((rt+1)*(rt+1)<=x) rt++;
return number[rt+1].second;
}
bool is_prime(int x){
for(int i=2;i*i<=x;i++) if(x%i==0) return false;
return true;
}
vector<int> num;
int cnt[4500*3000+5];
int calc(vector<int> a){
sort(a.begin(),a.end());
int s0=0;
for(int i=0;i+1<a.size();i++) s0+=gcd(a[i],a[i+1]);
vector<int> vec;
for(auto x:num){
int ns=s0;
auto it=lower_bound(a.begin(),a.end(),x);
if(it!=a.begin()&&it!=a.end()) ns-=gcd(*(it-1),*it);
if(it!=a.begin()) ns+=gcd(*(it-1),x);
if(it!=a.end()) ns+=gcd(x,*it);
if(cnt[ns]) vec.push_back(ns);
cnt[ns]++;
}
int mx=0;
for(auto s:vec) chkmax(mx,cnt[s]),cnt[s]=0;
return mx;
}
int tot=0;
vector<int> q[12005];
unordered_map<int,int> ch[12005];
int dfs(int d){
// printf("d=%d\n",d);
// for(auto i:num) printf("%d ",i);printf("\n");
if(d==4){
tot++;
q[tot]=num;
int s=0;
for(int i=0;i+1<num.size();i++) s+=gcd(num[i],num[i+1]);
for(int i=0;i<num.size();i++) ch[tot][s+num[i]]=-num[i];
return tot;
}
int mn=inf;
vector<int> best;
for(int _=1;_<=T;_++){
int len=rnd()%(lmax-lmin+1)+lmin;
vector<int> a;
for(int i=1;i<=len;i++) a.push_back(myrand());
int cur=calc(a);
if(cur<mn) mn=cur,best=a;
int failcnt=0;
for(int r=1;r<=T2&&failcnt<=MaxFailcnt;r++){
int p=rnd()%a.size(),d;
if(a[p]>1&&a[p]<4500) d=((rnd()&1)<<1)-1;
else if(a[p]>1) d=-1;
else d=1;
a[p]+=d;
int nw=calc(a);
if(nw<mn) mn=nw,best=a;
if(nw<cur) cur=nw,failcnt=0;
else a[p]-=d,failcnt++;
}
}
sort(best.begin(),best.end());
int u=++tot;
q[u]=best;
int s0=0;
for(int i=0;i+1<best.size();i++) s0+=gcd(best[i],best[i+1]);
unordered_map<int,vector<int>> cls;
for(auto x:num){
int ns=s0;
auto it=lower_bound(best.begin(),best.end(),x);
if(it!=best.begin()&&it!=best.end()) ns-=gcd(*(it-1),*it);
if(it!=best.begin()) ns+=gcd(*(it-1),x);
if(it!=best.end()) ns+=gcd(x,*it);
cls[ns].push_back(x);
}
for(auto &[k,v]:cls){
if(v.size()==1){
ch[u][k]=-v[0];
continue;
}
num.swap(v);
ch[u][k]=dfs(d+1);
num.swap(v);
}
return u;
}
void init(int c, int t){
for(int i=1;i<=4500;i++){
for(int x=i,j=2;j*j<=x;j++){
while(x%j==0){
number[i].first++;
x/=j;
}
}
number[i].second=i;
}
sort(number+1,number+4500+1);
if(c==1) for(int i=1;i<=35;i++) num.push_back(i);
else for(int i=1;i<=3000;i++) if(c!=2||is_prime(i)) num.push_back(i);
dfs(1);
}
int find_tastiness(int c, int m){
int p=1;
while(p>0){
int s=query_tastiness(q[p]);
p=ch[p][s];
}
return -p;
}
卡常后:
#include"pudding.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
int gcd(int a,int b){
if(!b) return a;
return gcd(b,a%b);
}
const int T=2000,T2=100,MaxFailcnt=20,lmin=5,lmax=5;
mt19937 rnd(19491001);
int query_tastiness(std::vector<int> a);
int Gcd[4505][4505];
pii number[4505];
int myrand(){
int x=rnd()%(4500*4500),rt=sqrt(x);
while((rt+1)*(rt+1)<=x) rt++;
return number[rt+1].second;
}
bool is_prime(int x){
for(int i=2;i*i<=x;i++) if(x%i==0) return false;
return true;
}
vector<int> num;
int cnt[4500*3000+5];
int calc(vector<int> a){
sort(a.begin(),a.end());
int s0=0;
for(int i=0;i+1<a.size();i++) s0+=Gcd[a[i]][a[i+1]];
vector<int> vec;
int i=0;
for(auto x:num){
int ns=s0;
while(i<a.size()&&a[i]<x) i++;
if(i&&i<a.size()) ns-=Gcd[a[i-1]][a[i]];
if(i) ns+=Gcd[a[i-1]][x];
if(i<a.size()) ns+=Gcd[x][a[i]];
if(cnt[ns]) vec.push_back(ns);
cnt[ns]++;
}
int mx=0;
for(auto s:vec) chkmax(mx,cnt[s]),cnt[s]=0;
return mx;
}
int tot=0;
vector<int> q[12005];
unordered_map<int,int> ch[12005];
int dfs(int d){
if(d==4){
tot++;
q[tot]=num;
int s=0;
for(int i=0;i+1<num.size();i++) s+=Gcd[num[i]][num[i+1]];
for(int i=0;i<num.size();i++) ch[tot][s+num[i]]=-num[i];
return tot;
}
int mn=inf;
vector<int> best;
for(int _=1;_<=T;_++){
int len=rnd()%(lmax-lmin+1)+lmin;
vector<int> a;
for(int i=1;i<=len;i++) a.push_back(myrand());
int cur=calc(a);
if(cur<mn) mn=cur,best=a;
int failcnt=0;
for(int r=1;r<=T2&&failcnt<=MaxFailcnt;r++){
int p=rnd()%a.size(),d;
if(a[p]>1&&a[p]<4500) d=((rnd()&1)<<1)-1;
else if(a[p]>1) d=-1;
else d=1;
a[p]+=d;
int nw=calc(a);
if(nw<mn) mn=nw,best=a;
if(nw<cur) cur=nw,failcnt=0;
else a[p]-=d,failcnt++;
}
}
sort(best.begin(),best.end());
int u=++tot;
q[u]=best;
int s0=0;
for(int i=0;i+1<best.size();i++) s0+=Gcd[best[i]][best[i+1]];
unordered_map<int,vector<int>> cls;
int i=0;
for(auto x:num){
int ns=s0;
while(i<best.size()&&best[i]<x) i++;
if(i&&i<best.size()) ns-=Gcd[best[i-1]][best[i]];
if(i) ns+=Gcd[best[i-1]][x];
if(i<best.size()) ns+=Gcd[x][best[i]];
cls[ns].push_back(x);
}
for(auto &[k,v]:cls){
if(v.size()==1){
ch[u][k]=-v[0];
continue;
}
num.swap(v);
ch[u][k]=dfs(d+1);
num.swap(v);
}
return u;
}
void init(int c, int t){
for(int i=1;i<=4500;i++) for(int j=1;j<=4500;j++) Gcd[i][j]=gcd(i,j);
for(int i=1;i<=4500;i++){
for(int x=i,j=2;j*j<=x;j++){
while(x%j==0){
number[i].first++;
x/=j;
}
}
number[i].second=i;
}
sort(number+1,number+4500+1);
if(c==1) for(int i=1;i<=35;i++) num.push_back(i);
else for(int i=1;i<=3000;i++) if(c!=2||is_prime(i)) num.push_back(i);
dfs(1);
}
int find_tastiness(int c, int m){
int p=1;
while(p>0){
int s=query_tastiness(q[p]);
p=ch[p][s];
}
return -p;
}
极限卡常(QOJ 2s,by Claude Opus 4.8):
#include"pudding.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
template<typename T> void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const int T=2000,T2=100,MaxFailcnt=20,lmin=5,lmax=5;
mt19937 rnd(19491001);
int query_tastiness(std::vector<int> a);
const int MAXV=4501;
static short G[MAXV][MAXV]; // gcd 表
pii number[4505];
int myrand(){
int x=rnd()%(4500*4500),rt=(int)sqrt((double)x);
while((rt+1)*(rt+1)<=x) rt++;
return number[rt+1].second;
}
bool is_prime(int x){
for(int i=2;i*i<=x;i++) if(x%i==0) return false;
return true;
}
vector<int> num;
const int CNTSZ=40005; // ns 最大约 27000
int cnt[CNTSZ];
int vecbuf[3005];
int calc(const vector<int>& av){
int len=(int)av.size();
int a[8];
for(int i=0;i<len;i++) a[i]=av[i];
for(int i=1;i<len;i++){ // 5 个元素插入排序
int k=a[i],j=i-1;
while(j>=0&&a[j]>k){a[j+1]=a[j];j--;}
a[j+1]=k;
}
int s0=0;
for(int i=0;i+1<len;i++) s0+=G[a[i]][a[i+1]];
const int* np=num.data();
int nn=(int)num.size();
int vlen=0,j=0,last=a[len-1];
for(int t=0;t<nn;t++){ // num 升序,a 升序 -> 双指针
int x=np[t];
while(j<len && a[j]<x) j++;
int ns;
if(j==0) ns=s0+G[a[0]][x];
else if(j==len) ns=s0+G[last][x];
else ns=s0 - G[a[j-1]][a[j]] + G[a[j-1]][x] + G[x][a[j]];
if(cnt[ns]) vecbuf[vlen++]=ns;
cnt[ns]++;
}
int mx=0;
for(int i=0;i<vlen;i++){int s=vecbuf[i]; if(cnt[s]>mx)mx=cnt[s]; cnt[s]=0;}
return mx;
}
int tot=0;
vector<int> q[12005];
unordered_map<int,int> ch[12005];
int dfs(int d){
if(d==4){
tot++;
q[tot]=num;
int s=0;
for(int i=0;i+1<(int)num.size();i++) s+=G[num[i]][num[i+1]];
for(int i=0;i<(int)num.size();i++) ch[tot][s+num[i]]=-num[i];
return tot;
}
int mn=inf;
vector<int> best;
vector<int> a; a.reserve(lmax);
for(int _=1;_<=T;_++){
int len=rnd()%(lmax-lmin+1)+lmin;
a.clear();
for(int i=1;i<=len;i++) a.push_back(myrand());
int cur=calc(a);
if(cur<mn){mn=cur;best=a;}
int failcnt=0;
for(int r=1;r<=T2&&failcnt<=MaxFailcnt;r++){
int p=rnd()%a.size(),dd;
if(a[p]>1&&a[p]<4500) dd=((rnd()&1)<<1)-1;
else if(a[p]>1) dd=-1;
else dd=1;
a[p]+=dd;
int nw=calc(a);
if(nw<mn){mn=nw;best=a;}
if(nw<cur){cur=nw;failcnt=0;}
else {a[p]-=dd;failcnt++;}
}
}
sort(best.begin(),best.end());
int u=++tot;
q[u]=best;
int s0=0;
for(int i=0;i+1<(int)best.size();i++) s0+=G[best[i]][best[i+1]];
unordered_map<int,vector<int>> cls;
{ // 分类同样用双指针 + gcd 表
int len=(int)best.size(),j=0,last=best[len-1];
const int* np=num.data(); int nn=(int)num.size();
for(int t=0;t<nn;t++){
int x=np[t];
while(j<len && best[j]<x) j++;
int ns;
if(j==0) ns=s0+G[best[0]][x];
else if(j==len) ns=s0+G[last][x];
else ns=s0 - G[best[j-1]][best[j]] + G[best[j-1]][x] + G[x][best[j]];
cls[ns].push_back(x);
}
}
for(auto &pr:cls){
auto &v=pr.second;
if(v.size()==1){ ch[u][pr.first]=-v[0]; continue; }
num.swap(v);
ch[u][pr.first]=dfs(d+1);
num.swap(v);
}
return u;
}
void init(int c,int t){
// O(V^2) 递推建 gcd 表:gcd(i,j)=gcd(j,i%j)
for(int i=0;i<MAXV;i++){ G[i][0]=(short)i; G[0][i]=(short)i; }
for(int i=1;i<MAXV;i++){
G[i][i]=(short)i;
for(int j=1;j<i;j++){
short g=G[j][i%j]; // 行 j 已完全算好,i%j<j
G[i][j]=g; G[j][i]=g;
}
}
for(int i=1;i<=4500;i++){
for(int x=i,j=2;j*j<=x;j++) while(x%j==0){number[i].first++;x/=j;}
number[i].second=i;
}
sort(number+1,number+4500+1);
if(c==1) for(int i=1;i<=35;i++) num.push_back(i);
else for(int i=1;i<=3000;i++) if(c!=2||is_prime(i)) num.push_back(i);
dfs(1);
}
int find_tastiness(int c,int m){
int p=1;
while(p>0){
int s=query_tastiness(q[p]);
p=ch[p][s];
}
return -p;
}
NOI2026 总结
感觉都是一些思维链比较长、短时间内可能做不完的题,但是如果时间充足的话感觉不难。
*QOJ17641 [APIO2026 中国区 T2] 蛋糕
sub1~sub3:
先做 sub2,考虑要将 \(\{-1,0,1\}\) 分别对应到 \(\{0,1,2\}\),于是构造集合 \(\{1,1\}\) 即可完成。
这启发我们通过其他数的和来缩小 \(x\) 的范围。于是构造 \(\{1,1,2,4,\dots,2^k\}\)。可以通过比较 \(\sum\limits_{i=0}^pa_i\) 与 \(a_{p+1}\) 确定 \(x\) 的位置,然后从 \(p\) 往下逐位判断。因为要做到 \(\log W\) 次,所以找 \(p\) 需要从大往小找。需要精细实现。
*sub4:
被 sub3 启发,发现 \(\log_3W\approx 6.9\),考虑三进制,所以要把 \(\{-1,0,1\}\) 对应到三进制一位的 \(\{0,1,2\}\)(从信息论的角度亦可看出),然后不会构造了。
(*)查询不应该带 \(x\),因为如果返回 \(0\) 就“几乎”把 \(x\) 确定了,从信息论的角度看显然不现实。发现 \(3^7<W+200\),考虑构造集合 \(\{1,2,3,\dots,3^7\}\),这样对于下标(从 \(1\) 开始)为 \(i\) 的数,若 \(i\le x\),\(a_i=i\);否则 \(a_i=i-1\)。比较 \(a[3^6]+a[2\times 3^6]\) 与 \(a[3\times 3^6]\) 发现可以确定出来三进制第 \(6\) 位。对于更低的位,由于已经有了一些数 \(r\),比较 \(a[r+3^i]+a[r+2\times 3^i]\) 与 \(a[r]+a[r+3\times 3^i]\) 即可(因为 \(a[r]=r\) 恒成立)。
#include"cake.h"
#include<bits/stdc++.h>
using namespace std;
const int pw[8]={1,3,9,27,81,243,729,2187};
vector<int> bake_cakes(int N, int W, int K){
if(W==3) return {1,1};
if(W==2000){
vector<int> c;
for(int i=1;i<=pw[7];i++) c.push_back(i);
return c;
}
vector<int> c{1};
for(int i=1;i<=W;i<<=1) c.push_back(i);
return c;
}
int find_tastiness(int m, int W, int K){
if(W==3){
int t=compare_tastiness({0,1},{2});
if(t==-1) return 3;
if(t==0) return 2;
return 1;
}
if(W==2000){
int ans=0;
for(int i=6;i>=0;i--){
vector<int> v1{ans+pw[i]-1,ans+2*pw[i]-1};
vector<int> v2{ans+3*pw[i]-1};
if(ans) v2.push_back(ans-1);
int t=compare_tastiness(v1,v2);
if(t==0) ans+=pw[i];
else if(t==1) ans+=2*pw[i];
}
return ans;
}
int p;
for(p=m-1;p>0;p--){
vector<int> v;
for(int i=0;i<p;i++) v.push_back(i);
if(compare_tastiness(v,{p})==0) break;
}
vector<int> v{p};
for(int i=p-1;i>0;i--){
v.push_back(i);
int tmp=compare_tastiness(v,{p+1});
if(tmp>0) v.pop_back();
if(tmp==0) break;
}
int ans=0;
for(auto i:v) ans+=(1<<(i-1));
return ans;
}
*QOJ189 I 君的商店
我是真的不会交互题啊
特殊性质:如果 \(1\) 在左侧,reverse 过来。考虑二分,显然是要跟 \(1\) 比,但是每次拿 \(1\) 个数比做不了,考虑拿相邻两个数,则两种返回值分别对应:
- \([0,0]\) 或 \([0,1]\)
- \([1,1]\) 或 \([0,1]\)
如此二分,最后剩一个未知数用奇偶性确定。
(*)核心点:每次钦定一个/一些数被确定显然做不到(否则奇偶性在特殊性质里不是必要条件),考虑确定两个数中的一个(但确定下来哪个不是提前知道的)。
考虑对三个数 \(a,b,c\),若 \(a\le b\):
- 若查询得到 \(a+b\le c\),则 \(a=0\);
- 若得到 \(a+b\ge c\),则 \(b\ge c\)。特别地,若 \(c=1\),则 \(b=1\)。
\(7n\) 做法:\(1\) 是最大值。直接花 \(2n\) 打擂台找出一个 \(1\),再用 \(5n\) 确定下来其他数,最后一个用奇偶性确定。
\(5n+\log\) 做法:不能找 \(1\),尝试利用特殊性质的做法,发现 case2 得到了 \(b\ge c\) 的性质,尝试根据此构造 非降子集,并在其上做特殊性质。具体地,令子序列最后一个元素为 \(c\),取还未被确定的两个数 \(a,b\)(\(a\le b\)):
- \(a+b\le c\),则确定 \(a=0\);
- \(a+b\ge c\),则子序列
push_back\(b\)。
最后会剩下一个未知数,与子序列末尾比较并交换,使最大值在子序列上。子序列跑完也会剩下一个未知数,将两个未知数比较一下,再求和与 \(1\) 比较一下,就可以确定一个,另一个用奇偶性确定。
#include"shop.h"
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int ans[N],s1[N],s2[N];
int get(vector<int> &v){
int x=v.back();
v.pop_back();
return x;
}
int solve(vector<int> &vec,bool asc){
if(!asc){
s1[0]=vec[0],s2[0]=vec.back();
if(!query(s1,1,s2,1)) reverse(vec.begin(),vec.end());
}
if(vec.size()==1) return vec[0];
ans[vec.back()]=1;
int l=0,r=vec.size()-2;
while(l<r){
int mid=(l+r)>>1;
s1[0]=vec[mid],s1[1]=vec[mid+1],s2[0]=vec.back();
int tmp=query(s1,2,s2,1);
if(!tmp) r=mid;
else l=mid+1;
}
for(int i=0;i<l;i++) ans[vec[i]]=0;
int cnt=0;
for(int i=l+1;i<vec.size();i++) ans[vec[i]]=1,cnt++;
return vec[l];
}
void find_price(int task_id, int n, int k, int res[]){
if(n==1){
res[0]=1;
return;
}
vector<int> vec(n);
iota(vec.begin(),vec.end(),0);
if(n==2){
int a=0,b=1;
s1[0]=a,s2[0]=b;
if(!query(s1,1,s2,1)) swap(a,b);
if(k) res[a]=0,res[b]=1;
else res[a]=res[b]=1;
return;
}
int p;
if(task_id==3) p=solve(vec,false);
else{
int a=get(vec),b=get(vec),c=get(vec),type;
vector<int> vec2{c};
while(true){
s1[0]=a,s2[0]=b;
if(!query(s1,1,s2,1)) swap(a,b);
s1[0]=a,s1[1]=b,s2[0]=c;
if(query(s1,2,s2,1)){
type=1;ans[a]=0;
if(vec.size()) a=get(vec);
else break;
}
else{
type=2;vec2.push_back(b),c=b;
if(vec.size()) b=get(vec);
else break;
}
}
int q;
if(type==1) q=b;
else q=a;
s1[0]=c,s2[0]=q;
if(query(s1,1,s2,1)) vec2.back()=q,swap(c,q);
p=solve(vec2,true);
s1[0]=p,s2[0]=q;
if(!query(s1,1,s2,1)) swap(p,q);
s1[0]=p,s1[1]=q,s2[0]=c;
if(query(s1,2,s2,1)) ans[p]=0,p=q;
else ans[q]=1;
}
for(int i=0;i<n;i++) if(i!=p) k^=ans[i];
ans[p]=k;
// for(int i=0;i<n;i++) printf("%d ",ans[i]);printf("\n");
memcpy(res,ans,sizeof(int)*n);
}
\(5n\) 做法:每个数都需要至多 \(5\) 的代价就被确定。考虑 \(5n+\log\) 做法中,把 case2 中 \(a,b\) 中留下来的数加入另一个子集,则两个子集对应位大小关系确定(第一个 \(\ge\) 第二个)。特别地,第一个子集中的 \(0\) 对应第二个子集的数也是 \(0\),所以从左往右,用扫描(相邻两数求和与最大值比较)代替二分,扫描的代价可以认为是第二个子集上数的代价,所以仍然满足均摊的要求。其余未知数均用取两个数求和与 \(1\) 比较(\(7n\) 做法后半部分)即可。
*QOJ16122 Sorted Pairs
很牛的一道题。
(*)发现答案下界是 \(n\),因为每行都至少需要一个点被操作。接下来考虑上界。
(√)观察样例,考虑对换,即对 \(i,j\) 满足 \(a_{i,1}<a_{j,1}\land a_{i,2}<a_{j,2}\),交换 \(a_{i,2},a_{j,1}\)。发现只有“嵌套”时:
不再能对换,注意到 \(2n\) 必须移动到右侧,\(1\) 必须移动到左侧,考虑构造轮换:
即可,代价是 \(n+1\)。前面的对换可以用 \(2\) 的代价将问题规模 \(-2\),所以代价上界是 \(n+1\)。
具体实现,考虑按左侧从小到大排序,右侧从上往下贪心,对 \(a_{i,2}\) 每次取最靠上满足 \(a_{j,2}>a_{i,2}\) 的 \(j\),易知 \(\forall i<k<j,\ a_{k,2}<a_{i,2}\),所以不会导致原本有匹配变为无匹配(顶多是交换一下匹配),所以这样的贪心不劣。
(*)考虑什么时候答案是 \(n\)。代价为 \(n\) 的结构应该满足每行恰有一个元素被轮换,且被轮换的元素应一步到位。所以一个轮换应该在左右两侧交替,所以是偶环,所以对于 \(2\nmid n\) 一定做不到 \(n\)。
还是按左侧从小到大排序,记右侧排列为 \(p\)。一个轮换,应该交替走两类边:
- 从下标 \(i\),走到满足 \(p_j>p_i\) 的 \(j\),对应原来从右侧走到左侧的一条边;
- 从下标 \(i\),走到满足 \(j<i\) 的 \(j\),对应原来从左侧走到右侧的一条边。
发现对于 \(i\),如果 \(\{p_j\mid 1\le j\le i\}=\{v\in Z\mid n-i+1\le v\le n\}\),则 \(1\sim i\) 与 \(i+1\sim n\) 独立(即不存在一个轮换同时与两部分有交)。
只要每部分长度都是偶数即可。构造:
从下标最小位置出发,当前在 \(i\),先走到满足 \(p_j>p_i\) 的最大的 \(j\)(\(i\leftarrow j\)),再走到满足 \(j<i\) 且 \(p_j\) 最小的 \(j\)(\(i\leftarrow j\)),当 \(i\) 走到下表最大的位置时,进入第二部分。第二部分,把剩余没走的位置相邻两项分为一组,从右往左一组一组走,先走到这一组中较小的位置(2 类边),在走到较大的位置(1 类边),最后走回起点。
证明:
先看第一部分。用 \(v\) 表示走过的点的 \(p\) 值,\(i\) 表示下标。从 \(v_0\) 开始。发现 \(v_2\) 存在,否则所有 \(>v_0\) 的数构成前缀,与我们的子问题划分矛盾。又发现 \(v_2\le v_0-2\),否则 \([1,i_1]\) 内有且仅有 \(v_0-1\sim n\),构成前缀,矛盾。所以 \(v_3\) 最坏也能选 \(v_0-1\),故存在。若 \(i_3<i_1\),因为 \(v_2\) 为 \([1,i_1]\) 最小值,所以要么 \(i_3\) 为 \(i_1\) 左侧第一个没选的数,要么 \(i_3>i_1\),可以利用 \([1,\max(i_1,i_3)]\) 约束 \(i_4\)……以此类推,每一步都存在。
\(n\) 的构造也有基于括号序列的做法。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T,typename Cmp=less<T>>
void chkmin(T &x,const T &y,Cmp cmp=Cmp()){x=min(x,y,cmp);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=1000005;
int n,vis[N],pos[N];
pii p[N];
vector<vector<int>> ans;
template<typename Cmp,int inf>
struct SegTree{
int tr[N<<2];
template<typename Fun>
void build(int x,int l,int r,const Fun &f){
if(l==r) return (void)(tr[x]=f(l));
int mid=(l+r)>>1;
build(x<<1,l,mid,f),build(x<<1|1,mid+1,r,f);
tr[x]=min(tr[x<<1],tr[x<<1|1],Cmp());
}
void upd(int x,int l,int r,int p,int v){
if(l==r) return (void)(tr[x]=v);
int mid=(l+r)>>1;
if(p<=mid) upd(x<<1,l,mid,p,v);
else upd(x<<1|1,mid+1,r,p,v);
tr[x]=min(tr[x<<1],tr[x<<1|1],Cmp());
}
int query(int x,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr) return tr[x];
int mid=(l+r)>>1,mn=inf;
if(ql<=mid) chkmin(mn,query(x<<1,l,mid,ql,qr),Cmp());
if(qr>mid) chkmin(mn,query(x<<1|1,mid+1,r,ql,qr),Cmp());
return mn;
}
};
SegTree<less<int>,inf> indtree;
SegTree<greater<int>,-inf> valtree;
bool solve0sub(int l,int r){
if((r-l+1)&1) return false;
int L=inf,R=-inf;
for(int i=l;i<=r;i++) pos[p[i].second]=i,chkmin(L,p[i].second),chkmax(R,p[i].second),vis[i]=0;
valtree.build(1,L,R,[&](int v){return pos[v];}),indtree.build(1,l,r,[&](int i){return p[i].second;});
vector<int> perm;
auto mark=[&](int cur){
vis[cur]=1;
valtree.upd(1,L,R,p[cur].second,-inf);
indtree.upd(1,l,r,cur,inf);
};
int cur=l;
while(true){
perm.push_back(p[cur].second),mark(cur);
cur=valtree.query(1,L,R,p[cur].second+1,R);
perm.push_back(p[cur].first),mark(cur);
if(cur==r) break;
cur=pos[indtree.query(1,l,r,l,cur)];
}
vector<int> v;
for(int i=r;i>=l;i--) if(!vis[i]) v.push_back(i);
for(int i=0;i<v.size();i+=2){
int x=v[i],y=v[i+1];
if(p[x].second>p[y].second) swap(x,y);
perm.push_back(p[x].second),perm.push_back(p[y].first);
}
ans.push_back(perm);
return true;
}
bool solve0(){
if(n&1) return false;
ans.clear();
int lst=0,mn=n+1;
for(int i=1;i<=n;i++){
chkmin(mn,p[i].second);
if(i==n-mn+1){
if(!solve0sub(lst+1,i)) return false;
lst=i;
}
}
printf("%d\n",n);
return true;
}
void solve1(){
printf("%d\n",n+1);
ans.clear();
set<int> num;
vector<int> rest;
for(int i=1;i<=n;i++) vis[i]=0,pos[p[i].second]=i,num.insert(p[i].second);
for(int i=1;i<=n;i++){
if(vis[i]) continue;
num.erase(p[i].second);
auto it=num.upper_bound(p[i].second);
if(it==num.end()){
rest.push_back(i);
continue;
}
ans.push_back({p[i].second,p[pos[*it]].first});
vis[i]=vis[pos[*it]]=1;
num.erase(it);
}
// printf("rest ");
// for(auto i:rest) printf("%d ",i);printf("\n");
reverse(rest.begin(),rest.end());
vector<int> perm;
for(int i=0,t=1;i<rest.size();i++,t^=1){
if(t) perm.push_back(p[rest[i]].second);
else perm.push_back(p[rest[i]].first);
}
perm.push_back(p[rest[0]].first);
ans.push_back(perm);
}
void __INIT__(){}
void __SOLVE__(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&p[i].first,&p[i].second);
sort(p+1,p+n+1);
if(!solve0()) solve1();
printf("%ld\n",ans.size());
for(auto &v:ans){
printf("%ld ",v.size());
for(auto i:v) printf("%d ",i);
printf("\n");
}
}
int main(){
int T=1;
scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
*QOJ1228 I 君的探险
很牛的一道题,感觉这些技巧的可扩展性很高。
暴力
直接对每个点维护当前的状态,然后扫 \(0\sim n-1\),逐个 modify,看哪些点状态改变了。
只需要对 \(0\sim n-2\) 做,改 \(i\) 后,每个点对都要查询一次,共 \(\frac{n(n-1)}2\) 次,刚好满足要求。
*求异或
逐二进制位处理,在第 \(i\) 位时,modify 所有这位为 \(1\) 的点,若一个点状态改变说明它与所有相邻点编号的异或和第 \(i\) 位为 \(1\)。
可以做:
- \(A\) 性质;
- \(C\) 性质:随便找一个点,
modify再对所有点query再modify回来找到其相邻点,然后利用异或和向两侧扩展,也可以转成 \(D\) 性质; - \(BD\) 性质:剥叶子。\(B\) 直接从大往小剥就行。\(D\) 性质发现 \(L_m,L_q\) 足够大,发现如果 \(u\) 是叶子,刚才算出的 \(u\) 处异或和为 \(s_u\),则
modify(u)后query(s[u]^u)一定改变了,然后modify(u)回来,但这样可能找到不是叶子的点,没有关系,我们只需要保证不漏,不需要不重。
**整体二分
考虑 \(AB\) 性质,我们发现只需要找出每个点相邻且比它编号小的点(或不存在)即可。
一个简单的想法是对每个点二分,把 \([l,mid]\cap[0,u)\) modify,看 query(u) 有没有改变,若改变了,则 \(r\leftarrow mid\),否则 \(l\leftarrow mid+1\)。
然后套一个整体二分。
(*)但问题是不存在怎么办,我本来的做法是在二分结束后 modify + query 验证,但是这样太浪费,发现可以整体二分一开始不传询问进去,然后每次把 \([mid+1,r]\) 都跟刚才一样查一遍,如果有边,就放到左侧询问,否则不管。设找到的边数是 \(m\),这个过程的 query 次数大概是 \(\frac{n}2\log_2 n+m\log_2 n\)。
可以做:
- \(AB\) 性质;
- 正解:随机化,有结论是每次都能期望找到 \(\frac{n}3\) 条边,利用
check,每次 \(n\leftarrow \#\{u\mid check(u)=0\}\),然后做刚才的整体二分即可。
#include"explore.h"
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
mt19937 rnd(19491001);
int p[N],id[N],cnt,vis[N];
vector<int> G[N];
void solve(int l,int r,const vector<int> &nds){
if(l==r){
for(auto id:nds){
report(p[l],p[id]),cnt--;
G[p[l]].push_back(p[id]),G[p[id]].push_back(p[l]);
}
return;
}
int mid=(l+r)>>1;
int j=0;
vector<int> L,R;
for(int i=l;i<=mid;i++){
for(;j<nds.size()&&nds[j]<=i;j++){
int e=0;
for(auto v:G[p[nds[j]]]) if(!vis[v]&&id[v]>=l&&id[v]<i) e^=1;
if(query(p[nds[j]])^e) L.push_back(nds[j]);
else R.push_back(nds[j]);
}
modify(p[i]);
}
for(;j<nds.size();j++){
int e=0;
for(auto v:G[p[nds[j]]]) if(!vis[v]&&id[v]>=l&&id[v]<=mid) e^=1;
if(query(p[nds[j]])^e) L.push_back(nds[j]);
else R.push_back(nds[j]);
}
for(int j=mid+1;j<=r;j++){
int e=0;
for(auto v:G[p[j]]) if(!vis[v]&&id[v]>=l&&id[v]<=mid) e^=1;
if(query(p[j])^e) L.push_back(j);
}
for(int i=l;i<=mid;i++) modify(p[i]);
solve(l,mid,L),solve(mid+1,r,R);
}
int stat[N];
void explore(int n, int m){
if(n<=500){
for(int i=0;i<n-1;i++){
modify(i);
for(int j=i+1;j<n;j++){
int ns=query(j);
if(ns!=stat[j]) report(i,j);
stat[j]=ns;
}
}
return;
}
iota(p,p+n,0);
if(n%10==7||n%10==8){
solve(0,n-1,{});
return;
}
cnt=m;
while(cnt){
shuffle(p,p+n,rnd);
for(int i=0;i<n;i++) id[p[i]]=i;
solve(0,n-1,{});
if(!cnt) break;
vector<int> np;
for(int i=0;i<n;i++){
if(vis[p[i]]||check(p[i])) vis[p[i]]=1;
else np.push_back(p[i]);
}
n=np.size();copy(np.begin(),np.end(),p);
}
}
*QOJ8553 Exchanging Kubic
第一次做缩连续段的最大子段和
考虑先对所有位置问一遍,得到具体值或 \(\le 0\),按正负缩连续段,然后只需要考虑- 段被完整包含的区间。
对于 +-+,问一下整体,如果不是两边 + 的值,那么可以确定 - 的值是多少,并将这三段合并为一个 +。否则得到 - 的绝对值 \(\ge\) 两侧 + 值的 \(\min\)。
考虑 + 的全局 \(\min\) 两侧,+-+-+。如果 +-+ 没有发生合并,则两侧 - 绝对值 \(\ge\) 中间 +,所以最大子段不会以中间 + 结尾(除非恰 \(\subseteq\) 中间这段 +,这种情况已经确定),可将 -+- 缩成一个 -,仍需保证之前绝对值大小的限制,而新段无限制。
发现一定能用最多 \(2\) 次查询删掉一个 - 段,而两端的 - 段无代价删除,所以总查询次数(算上一开始 \(n\) 次) \(\le n+2\times\left\lfloor\frac{n}{2}\right\rfloor\le 2n\)。
这个过程保证的是所有可能成为最大子段的和不变,所以对于一些包含不完整 + 段的区间,可能贡献到它的子段要么在内部(与不完整段无交),要么在边界,可由外部完整段减确定值得到。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=2005;
int n,tail=0,tot=0;
ll ans[N],sum[N<<1];
pii rng[N<<1];// range
int nxt[N<<1],pre[N<<1],vis[N<<1];
priority_queue<pli,vector<pli>,greater<pli>> pq;
void pb(int l,int r,int t,ll s){
tot++;
vis[tot]=0;
rng[tot]={l,r};
if(t) sum[tot]=s,pq.push({s,tot});
else sum[tot]=-1;
nxt[tail]=tot,pre[tot]=tail,nxt[tot]=0;
tail=tot;
}
void era(int u){
vis[u]=1;
nxt[pre[u]]=nxt[u],pre[nxt[u]]=pre[u];
}
void upd(int u,ll v){
auto [l,r]=rng[u];
// printf("upd [%d,%d] %lld\n",l,r,v);
for(int i=l;i<=r;i++) v-=ans[i];
for(int i=l;i<=r;i++) if(ans[i]<=0) ans[i]+=v,v=0;
}
bool solve(int b){
printf("? %d %d\n",rng[pre[b]].first,rng[nxt[b]].second);
fflush(stdout);
ll v;
scanf("%lld",&v);
if(v==sum[pre[b]]||v==sum[nxt[b]]) return false;
upd(b,v-sum[nxt[b]]-sum[pre[b]]);
rng[b]={rng[pre[b]].first,rng[nxt[b]].second},sum[b]=v;
era(pre[b]),era(nxt[b]);
pq.push({sum[b],b});
return true;
}
void __INIT__(){}
void __SOLVE__(){
scanf("%d",&n);
tail=tot=0;
for(int i=1;i<=n;i++) ans[i]=0;
while(pq.size()) pq.pop();
int lst=0,t=0;
ll s=0;
for(int i=1;i<=n;i++){
printf("? %d %d\n",i,i);
fflush(stdout);
ll v;
scanf("%lld",&v);
if(v>0) ans[i]=v;
if(i==1) lst=1,t=(v>0);
else if(t!=(v>0)){
pb(lst,i-1,t,s);
lst=i,t=(v>0),s=0;
}
s+=v;
}
pb(lst,n,t,s);
while(pq.size()){
auto [s,u]=pq.top();
pq.pop();
if(vis[u]) continue;
if(!pre[u]&&!nxt[u]) break;
if(pre[u]){
if(!pre[pre[u]]){
upd(pre[u],0),era(pre[u]);
pq.push({sum[u],u});
continue;
}
if(solve(pre[u])) continue;
}
if(nxt[u]){
if(!nxt[nxt[u]]){
upd(nxt[u],0),era(nxt[u]);
pq.push({sum[u],u});
continue;
}
if(solve(nxt[u])) continue;
}
if(pre[u]) rng[u].first=rng[pre[u]].first,upd(pre[u],-sum[u]),era(pre[u]);
if(nxt[u]) rng[u].second=rng[nxt[u]].second,upd(nxt[u],-sum[u]),era(nxt[u]);
}
if(sum[nxt[0]]<0) upd(nxt[0],0);
printf("! ");
for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
printf("\n");
fflush(stdout);
}
int main(){
int T=1;
scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
CF521D Shop
取对数,最终解形式为 一堆 1 + 一堆 2 + 一堆 3,类似将每个操作对对数的贡献算出来,排序,从大到小取即可,最后按 1,2,3 的顺序重排一遍。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef __int128_t int128;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=100005;
int k,n,m;
ll a[N];
vector<pii> op[N];
struct Op{
int id,type;
pll frac;
};
vector<Op> full,ans;
bool cmp(Op x,Op y){
return (int128)x.frac.first*y.frac.second>(int128)y.frac.first*x.frac.second;
}
void __INIT__(){}
void __SOLVE__(){
scanf("%d%d%d",&k,&n,&m);
for(int i=1;i<=k;i++) scanf("%lld",&a[i]);
for(int j=1;j<=n;j++){
int t,i,b;
scanf("%d%d%d",&t,&i,&b);
if(t==1){
if(b>a[i]) op[i].push_back({b-a[i],-j});
}
else if(t==2) op[i].push_back({b,j});
else full.push_back({j,2,{b,1}});
}
for(int i=1;i<=k;i++){
bool has1=false;
sort(op[i].begin(),op[i].end(),greater<pii>());
for(auto [d,j]:op[i]){
if(j<0){
if(has1) continue;
has1=true;
}
full.push_back({abs(j),j/abs(j),{a[i]+d,a[i]}});
a[i]+=d;
}
}
sort(full.begin(),full.end(),cmp);
for(int i=0;i<full.size()&&i<m;i++) ans.push_back(full[i]);
sort(ans.begin(),ans.end(),[&](Op x,Op y){return x.type<y.type;});
printf("%ld\n",ans.size());
for(int i=0;i<ans.size();i++) printf("%d ",ans[i].id);
printf("\n");
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
CF1217D Coloring Edges
大水题?
只需考虑 scc(其实没必要)。
手玩一下,猜测最多只需要两种颜色,要求每一种颜色的导出子图都是 DAG。于是考虑 dfs,对树边、横叉边、前向边、返祖边分别考虑,树边、横叉边、前向边都可以填 \(1\) 而仍能保持 DAG,把所有返祖边都填 \(2\),因为返祖边保证 \(dfn\) 严格下降,所以是 DAG。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=5005;
int n,m,ans[N];
vector<pii> G[N];
int vis[N],instack[N];
void dfs(int u){
vis[u]=instack[u]=1;
for(auto [v,id]:G[u]){
if(instack[v]) ans[id]=2;
else ans[id]=1;
if(!vis[v]) dfs(v);
}
instack[u]=0;
}
void __INIT__(){}
void __SOLVE__(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back({v,i});
}
for(int i=1;i<=n;i++) if(!vis[i]) dfs(i);
int k=0;
for(int i=1;i<=m;i++) chkmax(k,ans[i]);
printf("%d\n",k);
for(int i=1;i<=m;i++) printf("%d ",ans[i]);
printf("\n");
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
CF1470D Strange Housing
发现一个必要条件是每个点都有至少一个关键点(Teachers)相邻,即关键点为极大独立集。
考虑什么时候仍不合法,发现形如:
\(E=\{(1,2),(2,3),(3,4),(4,5),(5,6),(6,1)\}\)
\(S=\{1,4\}\)
于是考虑用 dfs 树构造极大独立集:在 dfs 过程中,能选就选。
证明:
经典但这里不需要的想法:只需证任意两个点均可达。刚才的构造给我们一种能一直向上走的感受,发现确实如此,每个点均能到根:若当前点与父亲均未被选,则当前点必然有返祖边连向一个被选了的点,所以总能向上走。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=300005;
int n,m,vis[N],col[N];
vector<int> G[N];
void dfs(int u){
vis[u]=1,col[u]=1;
for(int v:G[u]) if(vis[v]&&col[v]) col[u]=0;
for(int v:G[u]){
if(vis[v]) continue;
dfs(v);
}
}
void __INIT__(){}
void __SOLVE__(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) G[i].clear(),vis[i]=col[i]=0;
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(v),G[v].push_back(u);
}
dfs(1);
for(int i=1;i<=n;i++) if(!vis[i]){
printf("NO\n");
return;
}
vector<int> ans;
for(int i=1;i<=n;i++) if(col[i]) ans.push_back(i);
printf("YES\n%d\n",ans.size());
for(auto i:ans) printf("%d ",i);
printf("\n");
}
int main(){
int T=1;
scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
*CF1450C2 Errich-Tac-Toe (Hard Version)
这我能不会?
肯定要找到若干对称的构造,然后证明总有一个操作数 \(\le\left\lfloor\frac{k}{3}\right\rfloor\)。
笔者的想法是,把每条正斜线都钦定一个颜色,这样有 \(6\) 组对称,但每个点都被改了 \(3\) 次,只能做到 \(\frac{k}{2}\)。形如:
XOO OXO OOX
OXO OOX XOO
OOX XOO OXO
OXX XOX XXO
XOX XXO OXX
XXO OXX XOX
所有类似的做法都只能做到 \(\frac{k}{2}\),因为每个点只有两种可能,所以每个点总会被统计一半的次数。
(*)但其实不需要这么严格,考虑怎么让每个点有三种可能,考虑添加“保持不变”。发现每个 \(1\times 3,\ 3\times 1\) 子矩形,只需要有一个 O 和 一个 X 即可。所以构造这 \(6\) 种方案(其中 ? 表示不变),每个点只会被改两次,能做到 \(\frac{k}{3}\):
XO? ?XO O?X
?XO O?X XO?
O?X XO? ?XO
X?O OX? ?OX
OX? ?OX X?O
?OX X?O OX?
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=305;
int n,cnt[3][2];
char bd[N][N];
void __INIT__(){}
void __SOLVE__(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%s",bd[i]+1);
memset(cnt,0,sizeof(cnt));
int tot=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(bd[i][j]=='O') cnt[((i-j)%3+3)%3][0]++,tot++;
else if(bd[i][j]=='X') cnt[((i-j)%3+3)%3][1]++,tot++;
}
}
for(int a=0;a<3;a++){
for(int b=0;b<3;b++){
if(a==b) continue;
if(cnt[a][0]+cnt[b][1]<=tot/3){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(((i-j)%3+3)%3==a&&bd[i][j]!='.') bd[i][j]='X';
if(((i-j)%3+3)%3==b&&bd[i][j]!='.') bd[i][j]='O';
}
}
for(int i=1;i<=n;i++) printf("%s\n",bd[i]+1);
return;
}
}
}
}
int main(){
int T=1;
scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
P9820 [ICPC 2020 Shanghai R] Mine Sweeper II
看到除以 \(2\),尝试把 .X 交换。发现 .X 交换后,和不变(拆贡献为八相邻 .X 对可证)。
发现把 \(B\) 操作为 \(A\) 和 \(flip(A)\) 中,至少有一个满足要求。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=1005;
int n,m;
char a[N][N],b[N][N];
void __INIT__(){}
void __SOLVE__(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
for(int i=1;i<=n;i++) scanf("%s",b[i]+1);
int cnt=0;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cnt+=(a[i][j]!=b[i][j]);
if(cnt<=n*m/2){
for(int i=1;i<=n;i++) printf("%s\n",a[i]+1);
return;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]=='.') printf("X");
else printf(".");
}
printf("\n");
}
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
CF1364D Ehab's Last Corollary
发现长 \(k\) 的环上选 \(\left\lceil\frac{k}{2}\right\rceil\) 个点可以保证最多只有一处相邻,于是考虑选 \(\left\lceil\frac{k}{2}\right\rceil\) 个点作为候选的独立集,如果有直接连边就调整为环。
此时需要保证独立集存在一种遍历方式,时相邻两点距离为 \(2\)。考虑 dfs 生成树,取奇数/偶数层,总有一层有 \(\left\lceil\frac{k}{2}\right\rceil\) 个点,如果没有内部的边,则找到独立集,否则拉出这条边两端在树上的路径,发现黑白交替出现,最多 \(2\left\lceil\frac{k}{2}\right\rceil-1\le k\) 个点。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=100005;
int n,m,k,dep[N],cnt[2],fa[N],sel[N];
vector<int> G[N];
pii es[N];
int t;
vector<int> nds[2];
void dfs(int u,int f,int d){
dep[u]=d,cnt[d&1]++,nds[d&1].push_back(u),fa[u]=f;
for(auto v:G[u]){
if(v==f) continue;
if(!dep[v]) dfs(v,u,d+1);
}
}
void __INIT__(){}
void __SOLVE__(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(v),G[v].push_back(u);
es[i]={u,v};
}
dfs(1,1,1);
if(cnt[0]>cnt[1]) t=0;
else t=1;
for(int i=0;i<(k+1)/2;i++) sel[nds[t][i]]=1;
int u=-1,v=-1;
for(int i=1;i<=m;i++){
if(sel[es[i].first]&&sel[es[i].second]){
u=es[i].first,v=es[i].second;
break;
}
}
if(u==-1&&v==-1){
printf("1\n");
for(int i=0;i<(k+1)/2;i++) printf("%d ",nds[t][i]);
printf("\n");
return;
}
vector<int> v1,v2;
while(u!=v){
if(dep[u]>=dep[v]) v1.push_back(u),u=fa[u];
else v2.push_back(v),v=fa[v];
}
printf("2\n%ld\n",v1.size()+v2.size()+1);
for(auto i:v1) printf("%d ",i);
printf("%d ",u);
for(auto i:v2) printf("%d ",i);
printf("\n");
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
*QOJ2671 景点划分
(*)核心在于,在 \(a\le b\le c\) 的情况下,钦定 \(A,B\) 连通(如果是 \(A,C\) 连通,可以把 \(C\) 调为 \(B\),其余同理)。
然后发现 \(a\le b\le\frac{n}{2}\),于是考虑重心(笔者的想法是枚举相邻点,然后分讨,也能做一部分)。
做树,尝试把树分为两部分,包含重心的一部分给到 \(B\)(因为 \(b\) 更大)。若有一棵子树(含外子树)大小 \(\ge a\),则剩余部分大小 \(\ge n-\frac{n}{2}=\frac{n}{2}\ge b\)(重心性质)。否则,\(A\) 一定要包含当前重心,而 \(b\ge a\),所以 \(B\) 也一定要包含当前重心,无解。
扩展到图,考虑 dfs 生成树的重心。如果有一棵子树大小 \(\ge a\),和刚才一样。
否则,\(A\) 必然在若干连通的子树上,由于没有横叉边,所以必然含外子树,逐个枚举与外子树连通的子树,给到 \(A\),直到总大小 \(\ge a\)。由于不满足第一种情况,所以每棵子树大小 \(<a\),给到 \(A\) 的总大小 \(<2a\)。所以剩余部分 \(>n-2a\ge n-a-c=b\)。如果总大小始终 \(<a\),则与刚才类似,\(A,B\) 都必须包含当前重心,无解。
反套路点:对于图问题,如果你想重新 dfs 一遍,反而会破坏重心性质。
#include "split.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=100005;
int n,tot,dfn[N],low[N],sz[N];
pii partsz[3];
vector<int> ans;
vector<pii> G[N],near[N];
pii build(int u,int f){
low[u]=dfn[u]=++tot,sz[u]=1;
int mxsz=0;
pii mn{inf,-1};
for(auto &[v,t]:G[u]){
if(v==f){
t=1;
continue;
}
if(!dfn[v]){
t=1;
chkmin(mn,build(v,u));
chkmin(low[u],low[v]);
sz[u]+=sz[v],chkmax(mxsz,sz[v]);
near[u].push_back({v,sz[v]});
}
else t=0,chkmin(low[u],dfn[v]);
}
near[u].push_back({f,n-sz[u]});
return min(mn,{max(mxsz,n-sz[u]),u});
}
int vis[N];
void clear(int u,int f){
vis[u]=0;
for(auto [v,t]:G[u]){
if(v==f||!t) continue;
clear(v,u);
}
}
void dfs(int u,int &k,int c){
if(vis[u]) return;
if(!k) return;
vis[u]=1,ans[u]=c,k--;
for(auto [v,t]:G[u]) dfs(v,k,c);
}
vector<int> find_split(int n_, int a, int b, int c, vector<int> p, vector<int> q) {
n=n_;
partsz[0]={a,1},partsz[1]={b,2},partsz[2]={c,3};
ans=vector<int>(n,0);
sort(partsz,partsz+3);
for(int i=0;i<p.size();i++) G[p[i]].push_back({q[i],0}),G[q[i]].push_back({p[i],0});
int u=build(0,0).second;
reverse(near[u].begin(),near[u].end());
// printf("u=%d\n",u);
// for(auto [v,s]:near[u]) printf("(%d,%d) ",v,s);
// printf("\n");
for(int i=0;i<n;i++) vis[i]=1;
for(auto [v,s]:near[u]){
if(s>=partsz[0].first){
clear(v,u);
dfs(v,partsz[0].first,partsz[0].second);
for(int i=0;i<n;i++) if(!ans[i]) vis[i]=0;
dfs(u,partsz[1].first,partsz[1].second);
for(int i=0;i<n;i++) if(!ans[i]) ans[i]=partsz[2].second;
return ans;
}
}
if(!u) return ans;
int sum=near[u][0].second;
clear(near[u][0].first,u);
for(int i=(!!u);i<near[u].size();i++){
auto [v,s]=near[u][i];
if(low[v]<dfn[u]){
clear(v,u);
sum+=s;
if(sum>=partsz[0].first) break;
}
}
if(sum<partsz[0].first) return ans;
dfs(near[u][0].first,partsz[0].first,partsz[0].second);
for(int i=0;i<n;i++) if(!ans[i]) vis[i]=0;
dfs(u,partsz[1].first,partsz[1].second);
for(int i=0;i<n;i++) if(!ans[i]) ans[i]=partsz[2].second;
return ans;
}
*P6892/UVA1697/QOJ7665 [ICPC 2014 WF] Baggage
观察样例,猜测答案就是 \(n\)。
再观察构造,发现形如:先调为 ...AABBAABB...(两端可能有不规则处),然后再变为 A...AB...B。
算一下中间状态的结构,发现与 \(n\text{ mod }4\) 相关。
前半部分基本流程是:
BABABABA...BABABABA
ABBABABABA...BABAB A
ABBA BABA...BABABBAA
ABBAABBABA...B ABBAA
ABBAABBA ...BBAABBAA
后半部分就是轮流把 BB,AA 放到中间空缺出即可。
中间会有 corner case,笔者手玩出了 \(n\text{ mod }4=0,1\) 的,\(2,3\) 只做到了 \(n+1\),实际你写个搜就能找到 \(n\) 的做法,\(n=3\) 需要特判。
n=3
n=5
BABABABABA
ABBABABAB A
ABBA BABBAA
---
ABBAABB BAA
---
A AABBBBBAA
AAAAABBBBB
*n=6
BABABABABABA
ABBABABABAB A
---
ABBABABA BBAA
---
ABB ABAABBBAA
ABBAAAB BBBAA
A AAABBBBBBAA
AAAAAABBBBBB
*n=7
---
BABABABABABABA
ABBABABAB ABABA
ABBABA BBAABABA
ABBABAABBBAAB A
ABBA ABBBAABBAA
---
ABBAAAABBB BBAA
A AAAABBBBBBBAA
AAAAAAABBBBBBB
n=8
BABABABABABABABA
ABBABABABABABAB A
ABBA BABABABABBAA
ABBAABBABAB ABBAA
ABBAABBA BBAABBAA
---
---
ABBAA ABBBBAABBAA
ABBAAAAABBBB BBAA
A AAAAABBBBBBBBAA
AAAAAAAABBBBBBBB
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
int n;
void __INIT__(){}
void __SOLVE__(){
scanf("%d",&n);
if(n==3){
printf("2 to -1\n");
printf("5 to 2\n");
printf("3 to -3\n");
return;
}
if((n&3)==0){
int i=-1,j=(n<<1)-2;
while(j>i+1){
printf("%d to %d\n",j,i),i+=4;
printf("%d to %d\n",i,j),j-=4;
}
int lst=i;
i=0,j=lst+4;
while(j<=(n<<1)-1){
printf("%d to %d\n",i,lst),lst=i,i+=4;
printf("%d to %d\n",j,lst),lst=j,j+=4;
}
return;
}
if((n&3)==1){
int i=-1,j=(n<<1)-2;
while(j>i+1){
printf("%d to %d\n",j,i),i+=4;
printf("%d to %d\n",i,j),j-=4;
}
int lst=i;
printf("%d to %d\n",lst+3,lst),lst+=3;
i=0,j=lst+3;
while(j<=(n<<1)-1){
printf("%d to %d\n",i,lst),lst=i,i+=4;
printf("%d to %d\n",j,lst),lst=j,j+=4;
}
}
if((n&3)==2){
int i=-1,j=(n<<1)-2;
while(j>i+11){
printf("%d to %d\n",j,i),i+=4;
printf("%d to %d\n",i,j),j-=4;
}
printf("%d to %d\n",j,i);
printf("%d to %d\n",j-3,j);
printf("%d to %d\n",i+3,j-3);
int lst=i+3;
printf("%d to %d\n",lst+4,lst),lst+=4;
i=0,j=lst+5;
while(j<=(n<<1)-1){
printf("%d to %d\n",i,lst),lst=i,i+=4;
printf("%d to %d\n",j,lst),lst=j,j+=4;
}
}
if((n&3)==3){
int i=-1,j=(n<<1)-2;
while(j>i+13){
printf("%d to %d\n",j,i),i+=4;
printf("%d to %d\n",i,j),j-=4;
}
printf("%d to %d\n",i+9,i);
printf("%d to %d\n",i+6,i+9);
printf("%d to %d\n",i+13,i+6);
printf("%d to %d\n",i+4,i+13);
printf("%d to %d\n",i+10,i+4);
int lst=i+10;
j=i+14,i=0;
while(j<=(n<<1)-1){
printf("%d to %d\n",i,lst),lst=i,i+=4;
printf("%d to %d\n",j,lst),lst=j,j+=4;
}
}
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
CF1495E Qingshan and Daniel
一个单 \(\log\) 爆冲做法,使用了 AI 辅助卡常。
考虑直接模拟这个过程,发现会一直在一个环内绕圈直到一个点被删除,删除这个 robot 后,有两种可能:
- 下一个 robot 同队:把环上当前 robot 改为下个 robot;
- 不同队:跳过环上当前 robot 和环上的下个 robot。
然后大力维护可以单 \(\log\)。
但是这个看起来就比较大,其实可以简化,注意到刚才所谓“环上点”其实就是剩余每个连续段的开头。
考虑直接维护连续段,开一个可删堆记录每个连续段被删除的时刻(一个二元组,第一关键字表示转了几圈,第二关键字表示位置)。先把出牌数都记在连续段开头,被删除时再向后更新。删除一个连续段时,合并两侧的连续段。
注意第一步可能不在环上。另外还有很多细节,见代码。
关于卡常,拿一个类似线段树(其实叫“竞赛树”,用非递归线段树实现)的东西维护堆,因为无 pushdown,所以修改一个点后直接从叶子往上 pushup。你又发现元素只增加(),所以 increase_keypushup 到一半可以提前退出。
#pragma GCC optimize("Ofast","-fno-stack-protector","-funroll-loops")
#pragma GCC target("mmx,fxsr,sse,sse2,ssse3,fma,cx16,sse4.1,sse4.2,movbe,popcnt,aes,xsave,avx,f16c,abm,fsgsbase,avx2,bmi2,xsaveopt")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=1'000'000'007;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=5000005;
int n,seed,base;
ll ans[N];
int rnd(){
int ret=seed;
seed=((ll)seed*base+233)%MOD;
return ret;
}
void print(){
int mul=1;
for(int i=1;i<=n;i++) mul=(ll)mul*((((ans[i])^((ll)i*i))+1)%MOD)%MOD;
printf("%d\n",mul);
}
ll a[N];
struct Heap {
int tr[N<<1];
bool cmp(int x,int y){
if(a[x]!=a[y]) return a[x]<a[y];
return x<y;
}
void build(int n){
for(int i=1;i<=n;i++) tr[n+i-1]=i;
for(int i=n-1;i>=1;i--){
if(cmp(tr[i<<1],tr[i<<1|1])) tr[i]=tr[i<<1];
else tr[i]=tr[i<<1|1];
}
}
void update(int x){
for(x+=n-1;x>>1;x>>=1){
if(tr[x>>1]==tr[x^1]) break;
if(cmp(tr[x],tr[x^1])) tr[x>>1]=tr[x];
else tr[x>>1]=tr[x^1];
}
}
void erase(int x){
a[x]=infll;
update(x);
}
int top(){
int x=tr[1];
if(a[x]==infll) return -1;
return x;
}
} hp;
int pre[N],nxt[N],a0[N];
bitset<N> t;
int nxtInd[N],tail[N];
void erase(int x){nxt[pre[x]]=nxt[x],pre[nxt[x]]=pre[x];}
void merge(int u,int v,ll c=0){
ll tmp=a[v];
hp.erase(v);
ans[v]+=c,a[u]+=tmp-c,nxtInd[tail[u]]=v,tail[u]=tail[v];
erase(v);
hp.update(u);
}
void fill(int x,ll c=0){
ans[x]+=c;
ll s=0;
for(int i=x;;i=nxtInd[i]){
s+=ans[i];
ans[i]=min((ll)a0[i],s);
s-=ans[i];
if(i==tail[x]) break;
}
}
void __INIT__(){}
void __SOLVE__(){
int m;
scanf("%d%d",&n,&m);
int lst=0;
for(int i=1;i<=m;i++){
int p,k,b,w;
scanf("%d%d%d%d",&p,&k,&b,&w);
seed=b,base=w;
for(int j=lst+1;j<=p;j++) t[j]=rnd()&1,a[j]=rnd()%k+1;
lst=p;
}
int s=1;
bool flag=false;
if(t[n]==t[1]){
a[1]--,flag=true;
for(int i=2;i<=n;i++) if(t[i]!=t[1]){
s=i;
break;
}
if(s==1){
ans[1]=1;
print();
return;
}
}
for(int i=1;i<=n;i++) pre[nxt[i]=i%n+1]=i,tail[i]=i,a0[i]=a[i];
hp.build(n);
for(int i=1;i<=n;i++) if(t[pre[i]]==t[i]) merge(pre[i],i);
while(true){
int p=hp.top();
if(p==-1) break;
ll c=a[p]-1;
hp.erase(p);
int u=pre[p],v=nxt[p];
erase(p);
ans[p]++;
fill(p,c);
int d=1;
if(v<p) d++;
if(u==v){
a[v]-=d,ans[v]+=d;
fill(u,c);
break;
}
if(a[v]==c+d||t[u]!=t[v]) continue;
merge(u,v,c+d);
}
if(flag) ans[1]++;
print();
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
*线性做法
其实差点就会了
和刚才类似,考虑每个点先“超额支出”一些牌,然后在跑一圈补偿。
问题在于怎么计算出牌数。哪队先出完是好算的,\(\sum a\) 较大的队会先出完,相等则都会出完。
(*)然后先出完的队答案可以直接算,把先出完队的 robot 出牌数贡献到后面最近的对手上即可。
然后跑两圈补偿(因为是个环)。
#pragma GCC optimize("Ofast","-fno-stack-protector","-funroll-loops")
#pragma GCC target("mmx,fxsr,sse,sse2,ssse3,fma,cx16,sse4.1,sse4.2,movbe,popcnt,aes,xsave,avx,f16c,abm,fsgsbase,avx2,bmi2,xsaveopt")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=1'000'000'007;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=5000005;
int n,seed,base;
int t[N],a[N],ans[N];
ll cnt[2];
int rnd(){
int ret=seed;
seed=((ll)seed*base+233)%MOD;
return ret;
}
void print(){
// for(int i=1;i<=n;i++) printf("%d ",ans[i]);
// printf("\n");
int mul=1;
for(int i=1;i<=n;i++) mul=(ll)mul*((((ans[i])^((ll)i*i))+1)%MOD)%MOD;
printf("%d\n",mul);
}
void __INIT__(){}
void __SOLVE__(){
int m;
scanf("%d%d",&n,&m);
int lst=0;
for(int i=1;i<=m;i++){
int p,k,b,w;
scanf("%d%d%d%d",&p,&k,&b,&w);
seed=b,base=w;
for(int j=lst+1;j<=p;j++) t[j]=rnd()&1,a[j]=rnd()%k+1;
lst=p;
}
for(int i=n;i>=1;i--) t[i]^=t[1],cnt[t[i]]+=a[i];
int lose=(cnt[0]>cnt[1]);
if(t[1]!=lose) a[1]--;
ll s=0;
for(int i=1;i<=n;i++){
if(t[i]==lose) ans[i]=a[i],s+=a[i];
else{
ans[i]=min(s,(ll)a[i]);
s-=ans[i];
}
}
for(int i=1;i<=n;i++) if(t[i]!=lose){
s+=ans[i];
ans[i]=min(s,(ll)a[i]);
s-=ans[i];
}
if(t[1]!=lose) ans[1]++;
print();
}
int main(){
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
UOJ809. 【UNR #7】那些你不要的
玩一下 \(n=1,3,5\) 发现 \(n=1\) 答案为第 \(1\) 个数,\(n=3\) 答案为第 \(1,3\) 个数中较大值,\(n=5\) 为第 \(1,3,5\) 中次大值。
再看 \(n=7\),发现只有其奇数位能成为后继状态的奇数位,并且所有后继状态奇数位能且只能由当前奇数位删一个得到。对于 小青鱼,应该删最小值;对于 智者,应该删最大值。
所以答案是所有奇数位的较大中位数。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=1000005;
int n,a[N];
void __INIT__(){}
void __SOLVE__(){
scanf("%d",&n);
vector<int> v;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(i&1) v.push_back(a[i]);
}
sort(v.begin(),v.end());
printf("%d\n",v[v.size()>>1]);
}
int main(){
int T=1;
scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
QOJ15546 仙人掌
打表找规律,发现是斐波那契数列。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int n;
int main(){
scanf("%d",&n);
int a=0,b=1;
while(n--) swap(a,b),add(b,a);
printf("%d\n",a);
return 0;
}
官方证明太 Ad-hoc 了,在和 AI 的激烈交流下,得到基于留数定理的证明。
使用 AI 辅助生成,并人工审核
文中所有有理分式都需要约分并保证分母为首 \(1\) 多项式。
定义:
- 极点:对于有理分式 \(G(x)=\frac{A(x)}{B(x)}\),“极点”为分母的所有根;
- Laurent 展开:对函数 \(f(x)\),定义 \(f(x)\) 在 \(a\) 处的 Laurent 展开为 \(f(x)=\sum\limits_{n=-\infty}^{+\infty}c_n(x-a)^n\);
- 留数:函数 \(f(x)\) 在极点 \(a\) 处的 Laurent 展开式中,系数 \(c_{-1}\) 称为 \(f(x)\) 在 \(a\) 处的留数,记作 \(\operatorname{Res}(f,c)\)。
公式:
- 连乘求导:若 \(F(x)=(x-x_0)H(x)\),则 \(F'(x_0)=H(x_0)\)。
推论:
- 推论 1(单极点留数公式):若 \(a\) 为 \(G(x)=\frac{A(x)}{B(x)}\) 的一阶极点(即 \(a\) 不是 \(B\) 的重根),则其留数可通过洛必达法则快速求得:\[\operatorname{Res}(G, a)=\frac{A(a)}{B'(a)} \]证明:\(\operatorname{Res}(G, a)=\lim_{x\to a}(x-a)G(x)=\lim_{x\to a}\frac{(x-a)A(x)}{B(x)}=\frac{A(a)}{B'(a)}\)。
- 推论 2(全局留数定理推论):对于有理函数 \(G(x)=\frac{A(x)}{B(x)}\),若分母最高次幂比分子最高次幂 至少大 2(即 \(\deg(B) \ge \deg(A) + 2\)),则 \(G(x)\) 所有极点处的留数之和为 \(0\)。即:\[\sum_k \operatorname{Res}(G, x_k)=0 \]无穷远处 Laurent 展开形式为 \(\displaystyle\sum_{n=-\infty}^{\infty}c_nz^n\),易知此时 \(c_{-1}=0\),则 \(\operatorname{Res}(G,\infty)=0\),代入留数定理可证。
第一步:构造全局有理函数
考虑以 \(f_i\) 为主元,观察到求和式中包含 \(\prod_{j \ne i}(f_i - f_j)\),这启发我们定义全局多项式:
其导数在 \(x = f_i\) 处的值恰好为分母:\(F'(f_i) = \prod_{j \ne i} (f_i - f_j)\)。
对于分子 \(\prod_{j \ne i}(1 + f_i - f_i f_j)\),为了补齐 \(j=i\) 的项以形成全局对称,我们乘上对应的因子 \((1 + f_i - f_i^2)\),并定义:
于是,原式可被改写为(每一项其实都是在 \(f_i\) 处的极限):
这自然地引导我们构造出如下辅助有理函数:
第二步:分析极点并计算留数
由于连乘求导性质,注意到 \(H(f_i)F'(f_i)\) 其实是 \(H(x)F(x)\) 在 \(f_i\) 处导数,于是发现 \(S\) 为一部分留数和,考虑利用留数定理。
函数 \(G(x)\) 的分母包含两部分,对应两类极点,注意可能有高阶极点(分母有重根),导致无法裂项,但我们可以利用连续性微调 \(f\),排除重根影响。
第一类极点:这些极点的和就是 \(S\)。
第二类极点:\(x = \phi_1, \phi_2\),其中 \(\phi\) 为 \(x^2 - x - 1 = 0\) 的根。
同理
第三步:应用全局留数定理求和
检查 \(G(x)\) 的多项式度数:
分子 \(A(x)\) 的最高次数为 \(x^n\)。
分母 \(-(x^2 - x - 1)P(x)\) 的最高次数为 \(x^{n+2}\)。
满足 \(\deg(\text{分母}) = \deg(\text{分子}) + 2\),由推论 2 可知所有极点的留数和为 \(0\):
将第一部分替换为原式 \(S\),移项可得:
此式正是斐波那契数列的 Binet 通项公式。
最终结论
(其中 \(F_n\) 为斐波那契数列第 \(n\) 项,定义 \(F_1=1, F_2=1\))。该式的值是常数,与输入序列 \(f_i\) 完全无关。
推广
求和 \(\displaystyle S_n=\sum_{i=1}^n\prod_{j\ne i}\frac{c+bf_i-af_if_j}{f_i-f_j}\) 满足 \(S_n=bS_{n-1}+acS_{n-2}\)
*QOJ14950 Relay Jump
没看到输入了 \(s\)……不知道看到了能不能想出来。
第一想法是不变量,但由于没看到 \(s\),想不出来怎么设计。然后手玩操作,感觉很复杂,不会做。
(*)考虑不变量,显然要与被刺激的是谁有关,设 \(i\) 被刺激,跳向 \(j\),考虑 \(i\) 移动前的不变量。
发现 \(x_j-x_i=(2x_j-x_i)-x_j\),所以不变量为
直接起始、终止状态列个等式解一下就行了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=100005;
int n,s;
pii p[N],q[N];
pll sum{0,0};
void add(pll &x,pii y){x.first+=y.first,x.second+=y.second;}
void sub(pll &x,pii y){x.first-=y.first,x.second-=y.second;}
int main(){
scanf("%d%d",&n,&s);
for(int i=1;i<=n;i++) scanf("%d%d%d%d",&p[i].first,&p[i].second,&q[i].first,&q[i].second);
for(int i=1;i<=n;i++){
if(i==s) sub(sum,p[i]);
else add(sum,p[i]);
}
sum.first*=-1,sum.second*=-1;
for(int i=1;i<=n;i++) add(sum,q[i]);
sum.first/=2,sum.second/=2;
for(int i=1;i<=n;i++) if(sum.first==q[i].first&&sum.second==q[i].second){
printf("%d\n",i);
return 0;
}
return 0;
}
*QOJ19102 Monuments
先看 \(m=0\),把对称刻画为匹配,任意一组匹配中的两个点对称。分讨易知一对匹配的贡献为 \(|x_i+x_j|\),把 \(x\) 排序后最小配最大即可,如果 \(n\) 为奇数强行加一个 \(x_0=0\)。
对 \(m>0\),显然可以把固定且已经对称的一对点一起去掉。
(*)接下来的核心是,把固定点当成自由点参与匹配。不能匹配当且仅当两端都是固定点(对称的已经清除)。注意到不与固定点匹配的仍为最小配最大,这启发我们,一定是左侧 \(n/2\) 个点配右侧(显然能保证合法,无解当且仅当固定点数多于自由点数;最优性考虑交叉优于不交)。
然后自然地,考虑把左侧翻到右侧上面,对齐成二分图考虑,则把左侧坐标 \(\times(-1)\),然后贡献变为 \(|x_i-x_j|\)。后记第一行坐标为 \(x_i\),第二行坐标为 \(y_j\)。
注意到,最终匹配构造为分段形式,每一段要么是只有上下各一个自由点(第一类),要么固定点、自由点数量相同(第二类),并且段与段之间独立。第一类段是好做的,我们重点关注第二类怎么算。
考虑每个点何时系数为 \(+1\),何时为 \(-1\)。我们有 \(4\) 类点,按如下方式称呼:
- \(0\) 类:第一行自由点;
- \(1\) 类:第一行固定点;
- \(2\) 类:第二行自由点;
- \(3\) 类:第二行固定点。
记 \(tx_i,ty_j\) 为对应类别
以 \(0\) 类点为例,记 \(\displaystyle d_{0,i}=\sum_j[y_j\le x_i\land ty_j=3]-\sum_{i'}[x_{i'}\le x_i\land tx_{i'}=0]\),记一段 \([l,r]\) 对应 \(\displaystyle \Delta_{0,r}=\sum_{j=1}^r[ty_j=3]-\sum_{i=1}^r[tx_i=0]\)。\(x_i\) 贡献为正当且仅当 \(d_{0,i}\ge\Delta_{0,r}\),注意到若 \([l,r]\) 内固定点、自由点数量相同,则 \(\Delta_{0,l-1}=\Delta_{0,r}\),所以可以差分。
扫描 \(i\),注意到 \(\Delta_{*,i}\) 每次只变化 \(\mathcal{O}(1)\),直接拿桶维护即可,然后可以算出 \(sum_i\)。
则一段 \([l,r]\) 的贡献可以被记为 \(sum_r-sum_{l-1}\)。
然后 DP,要么从第一类段转移,要么从最短的以当前结尾的第二类段转义。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=500005;
template<typename T>
vector<T> merge(vector<T> a,vector<T> b){
vector<T> c;
int i=0,j=0;
while(i<a.size()&&j<b.size()){
if(a[i]<b[j]) c.push_back(a[i++]);
else c.push_back(b[j++]);
}
while(i<a.size()) c.push_back(a[i++]);
while(j<b.size()) c.push_back(b[j++]);
return c;
}
int n;
int dx[N],dy[N],d[4];// >d: *(-1); <=d: *1
ll s[4][N],sum[N],f[N];
int lst[N<<1];
ll get_cost(vector<int> X, vector<int> P){
vector<pii> pts_free,pts_fixed_neg2,pts_fixed_pos2;
vector<pii> pts_neg,pts_pos,pts_fixed;
for(int i=0,j=0;i<X.size();i++){
if(j<P.size()&&P[j]==i){
j++;
if(X[i]<0) pts_neg.push_back({-X[i],-1});
else pts_pos.push_back({X[i],1});
continue;
}
pts_free.push_back({X[i],0});
}
reverse(pts_neg.begin(),pts_neg.end());
pts_fixed=merge(pts_neg,pts_pos);
if(X.size()&1) pts_fixed=merge(pts_fixed,{(pii){0,1}});
int cnt=0;
for(int i=0;i<pts_fixed.size();i++){
cnt+=pts_fixed[i].second;
if(i+1>=pts_fixed.size()||pts_fixed[i].first!=pts_fixed[i+1].first){
if(pts_fixed[i].first==0) cnt=cnt&1;
if(cnt<0) for(int k=1;k<=(-cnt);k++) pts_fixed_neg2.push_back({-pts_fixed[i].first,1});
else if(cnt>0) for(int k=1;k<=cnt;k++) pts_fixed_pos2.push_back({pts_fixed[i].first,1});
cnt=0;
}
}
reverse(pts_fixed_neg2.begin(),pts_fixed_neg2.end());
vector<pii> pts=merge(pts_free,pts_fixed_neg2);
pts=merge(pts,pts_fixed_pos2);
if(pts.empty()) return 0;
// for(auto [x,t]:pts) printf("(%d,%d) ",x,t);
// printf("\n");
vector<pii> x,y;
for(int i=0,j=pts.size()-1;i<j;i++,j--){
x.push_back({-pts[i].first,pts[i].second});
y.push_back({pts[j].first,pts[j].second+2});
}
reverse(x.begin(),x.end()),reverse(y.begin(),y.end());
assert(x.size()==y.size());
n=x.size();
int delt[2]={0,0};
for(int i=0,j=0;i<n;i++){
delt[x[i].second&1]--;
while(j<n&&y[j]<=x[i]) delt[(y[j++].second&1)^1]++;
dx[i]=delt[x[i].second&1];
}
delt[0]=delt[1]=0;
for(int j=0,i=0;j<n;j++){
delt[y[j].second&1]--;
while(i<n&&x[i]<y[j]) delt[(x[i++].second&1)^1]++;
dy[j]=delt[y[j].second&1];
}
// for(int i=0;i<n;i++) printf("(%d,%d,%d) ",x[i].first,x[i].second,dx[i]);printf("\n");
// for(int i=0;i<n;i++) printf("(%d,%d,%d) ",y[i].first,y[i].second,dy[i]);printf("\n");
for(int i=0;i<n;i++){
// printf("%d %d %d %d\n",d[0],d[1],d[2],d[3]);
if(i) sum[i]=sum[i-1];
s[x[i].second][dx[i]+n]+=x[i].first,s[y[i].second][dy[i]+n]+=y[i].first;
// printf("%lld ",sum[i]);
if(d[x[i].second]<=dx[i]) sum[i]+=x[i].first;
else sum[i]-=x[i].first;
if(d[y[i].second]<=dy[i]) sum[i]+=y[i].first;
else sum[i]-=y[i].first;
// printf("%lld ",sum[i]);
sum[i]+=2*s[x[i].second][(--d[x[i].second])+n];
sum[i]-=2*s[x[i].second^3][(d[x[i].second^3]++)+n];
sum[i]+=2*s[y[i].second][(--d[y[i].second])+n];
sum[i]-=2*s[y[i].second^3][(d[y[i].second^3]++)+n];
// printf("%lld\n",sum[i]);
}
memset(lst,-1,sizeof(lst));
int dif=0;
for(int i=0;i<n;i++){
if(x[i].second&1) dif++;
else dif--;
if(y[i].second&1) dif++;
else dif--;
if(!(x[i].second&1)&&!(y[i].second&1)){
if(i) f[i]=f[i-1]+abs(x[i].first-y[i].first);
else f[i]=abs(x[i].first-y[i].first);
}
else f[i]=infll;
if(lst[dif+(n<<1)]!=-1) chkmin(f[i],f[lst[dif+(n<<1)]]+sum[i]-sum[lst[dif+(n<<1)]]);
else if(!dif) chkmin(f[i],sum[i]);
lst[dif+(n<<1)]=i;
}
if(f[n-1]>=infll) return -1;
return f[n-1];
}
*QOJ13081/P13273 [NOI2025] 数字树
80pts
dfs 序与标记序列 \(t_u\) 一一对应,\(t_u\) 表示 \(u\) 先走左子还是右子。DFS 序是优美的当且仅当不存在两个数交叉(即 1212)。
两对叶子,当且仅当如下两种情况会对 \(t_u\) 产生限制:
. .
|\ |\
1 x | \
|\ | \
2 x x x
|\ |\ |\
2 1 1 2 2 1
两个标 x 的点的 \(t\) 会绑定,于是连一条边。
由于保证有解,维护一下等价类,能 80pts。
*100pts
与 80pts 做法 完全不一样。
(*)发现 \(t_u\) 序列没什么好性质,考虑另一种做法,从一个合法的 dfs 序出发,每次 reverse 一个子树。这样记 \(S_u\) 为 \(u\) 子树内恰出现一次的颜色数,则所有 \(S\) 相同的点应恰 reverse 偶数次(注意若 \(|S|\le 1\),则没有这样的限制),设 \(|S|\ge 2\) 的 \(S\) 数量为 \(c\),则答案为
考虑维护,这种集合数量肯定要哈希,有两种想法:
- 对一个时刻,维护每个点的哈希;
- 对一个点,维护其时间轴(本质上是最终的 \(S_u\))。
维护之后,把结点按字典序排序,则答案可以通过计算相邻两个点何时不同得到,难点在排序。
由于笔者忘了 线段树合并,所以在想法 1 上,用持久化线段树+哈希二分做到了 \(\log^3\)。
事实上,有 线段树合并,想法 2 是自然且好做的,排序的比较函数直接线段树上二分即可做到 \(\log^2\)。
关于求答案的细节:对排序后第 \(i\) 个点 \(p_i\),设 \(i\) 和 \(i-1\) 第 \(x\) 位开始不同,\(S_{p_i}\) 次小值为 \(y\),则 \(i\) 在 \(\max(x,y)\) 处开始产生贡献。
*bonus: 单 \(\log\)
有启发意义。
把线段树拓展成 \(2\) 的幂,然后可以类似后缀数组,从下往上逐层排序,每层可通过左子为第一关键字,右子为第二关键字排序。
其余部分都不是瓶颈,复杂度 \(\mathcal{O}(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef unsigned long long ull;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=1'000'000'007;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
for(;b;b>>=1,a=(ll)a*a%MOD) if(b&1) mul=(ll)mul*a%MOD;
return mul;
}
const ull B=2237;
const int N=200005;
ull pw[N];
int n,ch[N<<1][2],val[N<<2];
struct Node{
int lc,rc,len;
int cnt,rk;
ull hsh;
Node(){lc=rc=len=cnt=0,hsh=0;}
} tr[N<<7];
int L,tot=0,rt[N<<2];
vector<int> nds[N<<1];
void pushup(int x,int l,int r){
int mid=(l+r)>>1;
tr[x].cnt=tr[tr[x].lc].cnt+tr[tr[x].rc].cnt;
tr[x].hsh=tr[tr[x].lc].hsh*pw[r-mid]+tr[tr[x].rc].hsh;
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x|y;
int z=++tot;
tr[z].len=r-l+1;
if(l==r) return z;
int mid=(l+r)>>1;
tr[z].lc=merge(tr[x].lc,tr[y].lc,l,mid),tr[z].rc=merge(tr[x].rc,tr[y].rc,mid+1,r);
pushup(z,l,r);
return z;
}
void change(int &x,int l,int r,int p){
tr[++tot]=tr[x],x=tot;
tr[x].len=r-l+1;
if(l==r) return tr[x].cnt^=1,tr[x].hsh^=1,void();
int mid=(l+r)>>1;
if(p<=mid) change(tr[x].lc,l,mid,p);
if(p>mid) change(tr[x].rc,mid+1,r,p);
pushup(x,l,r);
}
void dfs(int u){
if(u<(n<<1)){
for(int i=0;i<2;i++){
dfs(ch[u][i]);
rt[u]=merge(rt[u],rt[ch[u][i]],1,L);
}
}
else change(rt[u],1,L,val[u]);
}
ull print(int x,int l,int r){
assert(!x||tr[x].len==r-l+1);
if(l==r){
printf("%d",tr[x].cnt);
assert(tr[x].cnt==tr[x].hsh);
return tr[x].hsh;
}
int mid=(l+r)>>1;
ull hsh=print(tr[x].lc,l,mid)*pw[r-mid]+print(tr[x].rc,mid+1,r);
assert(tr[x].hsh==hsh);
assert(tr[x].cnt==tr[tr[x].lc].cnt+tr[tr[x].rc].cnt);
assert(tr[x].hsh==tr[tr[x].lc].hsh*pw[r-mid]+tr[tr[x].rc].hsh);
return hsh;
}
int cnt[N<<3],mxr=1;
void sort(int l){
int m=nds[l].size();
vector<pii> onds(m);
for(int i=0;i<=mxr;i++) cnt[i]=0;
for(int i=0;i<m;i++) onds[i]={nds[l][i],tr[tr[nds[l][i]].rc].rk},cnt[onds[i].second]++;
for(int i=1;i<=mxr;i++) cnt[i]+=cnt[i-1];
for(int i=m-1;i>=0;i--) nds[l][--cnt[onds[i].second]]=onds[i].first;
onds=vector<pii>(m);
for(int i=0;i<=mxr;i++) cnt[i]=0;
for(int i=0;i<m;i++) onds[i]={nds[l][i],tr[tr[nds[l][i]].lc].rk},cnt[onds[i].second]++;
for(int i=1;i<=mxr;i++) cnt[i]+=cnt[i-1];
for(int i=m-1;i>=0;i--) nds[l][--cnt[onds[i].second]]=onds[i].first;
mxr=0;
for(int i=0;i<m;i++){
if(!i||tr[nds[l][i-1]].hsh!=tr[nds[l][i]].hsh) mxr++;
tr[nds[l][i]].rk=mxr;
}
}
int s;bool same;
int diff(int x,int y,int l,int r){
if(s+tr[x].cnt<2||same&&tr[x].hsh==tr[y].hsh){
s+=tr[x].cnt,same&=(tr[x].hsh==tr[y].hsh);
return r+1;
}
if(l==r) return l;
int mid=(l+r)>>1,tmp;
if((tmp=diff(tr[x].lc,tr[y].lc,l,mid))<=mid) return tmp;
return diff(tr[x].rc,tr[y].rc,mid+1,r);
}
int ans[N<<1];
void __INIT__(){
pw[0]=1;
for(int i=1;i<N;i++) pw[i]=pw[i-1]*B;
}
void __SOLVE__(){
scanf("%d",&n);
for(int i=1;i<(n<<1);i++) scanf("%d%d",&ch[i][0],&ch[i][1]);
for(int i=1;i<=n;i++){
int a,b;
scanf("%d%d",&a,&b);
val[a]=val[b]=i;
}
for(L=1;L<n;L<<=1);
dfs(1);
for(int i=1;i<=tot;i++){
if(tr[i].len>1) nds[tr[i].len].push_back(i);
else tr[i].rk=tr[i].cnt;
}
for(int l=2;l<=L;l<<=1) nds[l].push_back(0),sort(l);
for(int i=1;i<nds[L].size();i++){
s=0,same=true;
int l=diff(nds[L][i],nds[L][i-1],1,L);
if(l<=n) ans[l]++;
}
for(int i=1;i<=n;i++) printf("%d\n",qpow(2,(n<<1)-1-(ans[i]+=ans[i-1])));
}
int main(){
int T=1;
scanf("%*d");
__INIT__();
while(T--) __SOLVE__();
return 0;
}
*CF2180H2 Bug Is Feature (Conditional Version)
递归形式的规律往往可以写成简单的 k-进制 形式。
考虑求 SG,只需分析单个游戏。设 \(d=b-a=c-b\),则可以用二元组 \((c,d)\) 记录状态,转移为:
- \((c,d)\rightarrow (c+d,d)\):对应 \(a\leftarrow 2c-b\);
- \((c,d)\rightarrow (c+2d,2d)\):对应 \(b\leftarrow 2c-a\)。
由于有上界,从大往小转移,考虑将 \((c,d)\) 改写为 \((\left\lfloor\frac{x-c}{d}\right\rfloor,1)\),设 SG 函数 \(f_{i,j}\),则 \(f_{i,j}=\text{mex}(f_{i-j,j},f_{i-2j,2j})\)。
注意到 \(f_{i,2^t}=f_{\left\lfloor\frac{i}{2^t}\right\rfloor,1}\),所以舍去第二维,则 \(f_i=\text{mex}(f_{i-1},f_{\left\lfloor\frac{i-2}{2}\right\rfloor})\)。然后开始打表找规律。
(*)注意到扔掉 0 后,12 交替出现,把 0 改为 .,把 12 改为 x。
(√)观察:
.xxx.x.x.xxx.xxx.xxx.x.x.xxx.x.x.xxx.x.x.xxx.xxx
奇数位都是 x,去掉:
.x...x.x.x...x...x...x.x
偶数位都是 .,去掉:
x.xxx.x.x.xx
发现此时进入上级结构,为上一轮 .x 反转的序列,而第一轮平移 1 之后也一样。
发现 \(i\) 是 x 当且仅当 \(i+1\) 结尾有偶数个 \(1\)。
发现我们要求的实际上是 \(f\) 的前缀异或和,所以考虑算 \(i\) 及以前有多少个 x,发现数位 DP 一下即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll a,b,c,l,r;
ll f[2][2],g[2][2];
int calcs(ll x){
x++;
memset(f,0,sizeof(f));
f[1][0]=1;
for(int i=60;i>=0;i--){
memset(g,0,sizeof(g));
for(int a=0;a<2;a++) for(int b=0;b<2;b++){
for(int v=0;v<=(a?((x>>i)&1):1);v++){
g[a&&(v==((x>>i)&1))][v?(b^1):0]+=f[a][b];
}
}
memcpy(f,g,sizeof(g));
}
ll cnt=f[0][0]+f[1][0]-1;
int res=0;
if((cnt>>1)&1) res^=3;
if(cnt&1) res^=1;
return res;
}
int calcf(ll x){
if(!x) return 0;
return calcs(x)^calcs(x-1);
}
void __SOLVE__(){
scanf("%d",&n);
int sg=0;
for(int i=1;i<=n;i++){
scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&l,&r);
ll d=b-a;
ll nl=(l-c)/d,nr=(r-c)/d;
if(nl==nr){
if((r-l+1)&1) sg^=calcf(nl);
}
else{
if(((nl+1)*d-l+c)&1) sg^=calcf(nl);
if(d&1) sg^=calcs(nr-1)^calcs(nl);
if((r-c-nr*d+1)&1) sg^=calcf(nr);
}
}
if(!sg) printf("Feature\n");
else printf("Bug\n");
}
int main(){
int T=1;
scanf("%d",&T);
while(T--) __SOLVE__();
return 0;
}

浙公网安备 33010602011771号