集训做题杂记1
[CTS2024] 众生之门
小清新构造题。
观察大样例可以发现答案不大于 \(3\),感性猜测可以在路径长度不超过 \(3\) 的情况下遍历整棵树,事实也确实如此。
进一步考虑答案一般为 \(0\) 和 \(1\),只有 \(n\) 比较小或者图为菊花时答案是固定的。
所以我们对较小的 \(n\) 跑暴力,菊花图特判,对其余情况随机一个排列,每次交换两项,直到答案小于等于 \(1\) 输出即可。
由于答案值域是 \(O(n)\) 的,所以期望也是 \(O(n)\) 次找到答案,复杂度没问题。
#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define PLI pair<ll,int>
#define PIL pair<int,ll>
#define PLL pair<ll,ll>
#define fi first
#define se second
#define YES() cout<<"YES\n",0
#define NO() cout<<"NO\n",0
#define Yes() cout<<"Yes\n",0
#define No() cout<<"No\n",0
using ll=long long;
using uint=unsigned int;
using ull=unsigned long long;
using lb=long double;
const int N=5e4+5;
int T,n,s,t,dep[N],p[N],ANS,ans[N];
int siz[N],wc[N],top[N],fa[N];
vector<int>e[N];
void dfs1(int now,int f){
siz[now]=1,wc[now]=0,fa[now]=f;
for(auto it:e[now]){
if(it!=f){
dep[it]=dep[now]+1;
dfs1(it,now);
siz[now]+=siz[it];
if(siz[it]>siz[wc[now]]) wc[now]=it;
}
}
}
void dfs2(int now,int Top){
top[now]=Top;
if(wc[now]) dfs2(wc[now],Top);
for(auto it:e[now]){
if(it!=fa[now]&&it!=wc[now]) dfs2(it,it);
}
}
int LCA(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
bool flower(){
for(int i=1;i<=n;i++) if(e[i].size()==n-1) return 1;
return 0;
}
int dis(int x,int y){return dep[x]+dep[y]-2*dep[LCA(x,y)];}
mt19937 rd(std::random_device{}());
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin>>T;
while(T--){
cin>>n>>s>>t;
for(int i=1;i<=n;i++) e[i].clear();
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
dep[1]=0;dfs1(1,1),dfs2(1,1);
if(n<=8){
ANS=10;
for(int i=1;i<=n;i++) p[i]=i;
do{
if(p[1]!=s||p[n]!=t) continue;
int tmp=0;
for(int i=2;i<=n;i++) tmp^=dis(p[i],p[i-1]);
if(tmp<ANS){
ANS=tmp;
for(int i=1;i<=n;i++) ans[i]=p[i];
}
}while(next_permutation(p+1,p+n+1));
for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
cout<<"\n";
}else{
p[1]=s,p[n]=t;
// for(int i=2;i<=n-1;i++) ans[i]=p[i]=i-1+(i-1>=s)+(i-1+(i-1>=s)>=t);
for(int i=1;i<=n;i++)if(i!=s&&i!=t) p[i-(i>s)-(i>t)+1]=i;
if(flower()){
for(int i=1;i<=n;i++) cout<<p[i]<<" ";
cout<<"\n";
continue;
}
int tmp=0;
for(int i=2;i<=n;i++) tmp^=dis(p[i],p[i-1]);
while(tmp>1){
int l=rd()%(n-2)+2,r=rd()%(n-2)+2;
while(l==r) r=rd()%(n-2)+2;
tmp^=dis(p[l],p[l-1])^dis(p[l],p[l+1])^dis(p[r],p[r-1])^dis(p[r],p[r+1]);
swap(p[l],p[r]);
tmp^=dis(p[l],p[l-1])^dis(p[l],p[l+1])^dis(p[r],p[r-1])^dis(p[r],p[r+1]);
}
for(int i=1;i<=n;i++) cout<<p[i]<<" ";
cout<<"\n";
}
}
}
P10813 【MX-S2-T4】 换
对于这种排序网络的题,可以考虑一个经典 trick,将序列 \(\{A_n\}\) 重赋值为 \(\{B_n(V)\}\) 满足:\(B_{i}(V)=[A_i\le V]\)。
一个序列 \(\{A_n\}\) 合法,当且仅当 \(\forall V,\{B_n(V)\}\) 合法。
考虑直接预处理出所有 01 序列是否合法,这是 \(O(m2^n)\) 的。
发现序列 \(A\) 离散化后只有 \(O(n)\) 个 \(V\) 是有意义的,考虑状压 DP,记 \(dp_{i,S}\) 表示离散化后 \(\{B_n(i)\}=S\) 且 \(\exists x,A_x=i\) 的方案数。
转移就是填入 \(i+1\),有 \(dp_{i+1,S}=\sum_{T\subset S}dp_{i,T}\)。
直接上子集枚举是 \(O(n3^n)\) 的,不太可行,考虑每一层 DP 前做一遍高维前缀和,于是复杂度变为 \(O(n^22^n)\)。
总时间复杂度 \(O(n^2+m)2^n\)。
#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define PLI pair<ll,int>
#define PIL pair<int,ll>
#define PLL pair<ll,ll>
#define fi first
#define se second
#define YES() cout<<"YES\n",0
#define NO() cout<<"NO\n",0
#define Yes() cout<<"Yes\n",0
#define No() cout<<"No\n",0
using ll=long long;
using uint=unsigned int;
using ull=unsigned long long;
using lb=long double;
const int N=18,M=505,mod=1e9+7;
ll n,V,m,dp[1<<N],SS[1<<N],ans;
int x[M],y[M];
bool f[1<<N],p[N+5];
bool check(int S){
for(int i=1;i<=n;i++) p[i]=(S>>(i-1))&1;
for(int i=1;i<=m;i++){
if(p[x[i]]>p[y[i]]) swap(p[x[i]],p[y[i]]);
}
bool tmp=1;
for(int i=1;i<n;i++) tmp&=(p[i]<=p[i+1]);
return tmp;
}
ll q_pow(ll x,ll b){
ll c=1;
while(b){
if(b&1) c=c*x%mod;
x=x*x%mod,b>>=1;
}
return c;
}
ll inv(ll x){return q_pow(x,mod-2);}
ll C(ll x,ll y){
if(x<y||y<0)return 0;
ll ret=1;
for(int i=1;i<=y;i++) ret=ret*(x-i+1)%mod*inv(i)%mod;
return ret;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin>>n>>V>>m;
for(int i=1;i<=m;i++) cin>>x[i]>>y[i];
for(int S=0;S<(1<<n);S++) f[S]=check(S);
dp[0]=1;
for(int i=1;i<=n;i++){
for(int S=0;S<(1<<n);S++) SS[S]=dp[S];
for(int j=0;j<n;j++){
for(int S=0;S<(1<<n);S++){
if(S&(1<<j)) SS[S]=(SS[S]+SS[S^(1<<j)])%mod;
}
}
for(int S=0;S<(1<<n);S++) dp[S]=(SS[S]-dp[S]+mod)*f[S]%mod;
ans=(ans+C(V,i)*dp[(1<<n)-1]%mod)%mod;
}
cout<<ans<<"\n";
}
The only survival
发现每次可以钦定一堆点满足 \(dis_{1,n}\in [1,k] \cup \{> k\}\),这里 \(>k\) 当作同一类点,因为对 \(dis_{1,n}=k\) 没有意义。同时 \(dis_{1,n}\) 必须钦定为 \(k\)。
新加入一个点 \(v\) 时,对于一条边 \(<u,v,w>\) 有 \(w\in[dis_{1,v}-dis_{1,u},lim]\) 乘法原理即可算出总方案数,同时注意必须存在一条边满足 \(w=dis_{1,v}-dis_{1,u}\) ,减掉 \(\forall <u,v,w>,w>dis_{1,v}-dis_{1,u}\) 的方案数即可,快速求方案数可以记录 \(cnt_i=\sum_x [dis_{1,x}=i]\)。
时间复杂度是划分数,所以 \(n\le 13\) 能跑。
#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define PLI pair<ll,int>
#define PIL pair<int,ll>
#define PLL pair<ll,ll>
#define fi first
#define se second
#define YES() cout<<"YES\n",0
#define NO() cout<<"NO\n",0
#define Yes() cout<<"Yes\n",0
#define No() cout<<"No\n",0
using ll=long long;
using uint=unsigned int;
using ull=unsigned long long;
using lb=long double;
const int N=15;
ll n,lim,k,mod,cnt[N],ans;
ll q_pow(ll x,ll b){
ll c=1;
while(b){
if(b&1) c=c*x%mod;
x=x*x%mod,b>>=1;
}
return c;
}
ll inv(ll x){return q_pow(x,mod-2);}
ll fac[N],C[N][N];
void init(int x){fac[0]=1;for(int i=1;i<=x;i++) fac[i]=fac[i-1]*i%mod;}
void dfs(int dep,int num,ll tmp){
if(dep>k){
ll tmpv=1;
for(int i=0;i<=k;i++) tmpv=tmpv*q_pow(lim-(k-i),cnt[i])%mod;
return ans=(ans+tmp*q_pow(tmpv,n-num)%mod*q_pow(lim,(n-num)*(n-num-1)/2)%mod)%mod,void();
}else{
ll tmpv=1,subv=1;
for(int i=0;i<dep;i++) tmpv=tmpv*q_pow(lim-(dep-i)+1,cnt[i])%mod,subv=subv*q_pow(lim-(dep-i),cnt[i])%mod;
tmpv=(tmpv+mod-subv)%mod;
for(int i=(dep==k);num+i<=n;i++) cnt[dep]=i,dfs(dep+1,num+i,tmp*q_pow(tmpv,i)%mod*q_pow(lim,i*(i-1)/2)%mod*C[n-num-1][i-(dep==k)]%mod);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
cin>>n>>k>>lim>>mod;
if(lim<k) return cout<<"0\n",0;
C[0][0]=1;
for(int i=1;i<=n;i++){
C[i][0]=1;
for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
init(n);cnt[0]=1,n--;
dfs(1,0,1);
cout<<ans<<"\n";
}
神秘式子一则
原题钦定顺序然后就是求将 \(n\) 个不同小球放进任意个相同盒子使得每个盒子小球数量 \(\ge2\) 的方案数。
每个盒子至少放一个小球方案数是简单的斯特林数,考虑对只有一个小球的盒子个数容斥。
于是有:
考虑拆开斯特林数:
后半是个前缀和的形式,可以预处理出来,于是复杂度 \(O(n)\)。
TEST_90
从矩阵乘法的形式来理解区间历史和问题,继而可以推广到任意双半群模型,算是个经典 trick 了。
排序后扫描线,转换为历史和问题是平凡的。
设线段树节点维护 \(\{S,PS,L\}\),代表当前区间和,历史和,区间长度,考虑两个 tag 所对应的矩阵。
Xor:
Tim:
直接做就行,但你发现直接矩乘是过不去的。
这个里面说的很好:
矩阵乘法更多是用来理解标记的,而不是用在代码实现上的。
发现矩阵中只有四个值会变化,其余值都是固定的,所以只维护这四个值就可以。
struct M{
ll d[4];
ll& operator[](size_t x){return d[x];}
friend M operator*(M x,M y){
return {x[0]*y[1]+y[0],x[1]*y[1],x[2]+x[1]*y[2],x[3]+y[3]+x[0]*y[2]};
}
};
const M I={0,1,0,0};
M xort(){return {1,-1,0,0};}
M tim(){return {0,1,1,0};}
其中 \(d_0: L\rightarrow S,d_1: S\rightarrow S,d_2: S\rightarrow PS,d_3: L\rightarrow PS\)。
然后矩阵乘法常数就很小了。
#include<bits/stdc++.h>
using namespace std;
#define PII pair<int,int>
#define PLI pair<ll,int>
#define PIL pair<int,ll>
#define PLL pair<ll,ll>
#define fi first
#define se second
#define YES() cout<<"YES\n",0
#define NO() cout<<"NO\n",0
#define Yes() cout<<"Yes\n",0
#define No() cout<<"No\n",0
using ll=long long;
using uint=unsigned int;
using ull=unsigned long long;
using lb=long double;
const int N=1e6+5;
int n,d,m,a[N],l[N],lst[N];
ll ans[N];
vector<PII>q[N];
struct M{
ll d[4];
ll& operator[](size_t x){return d[x];}
friend M operator*(M x,M y){
return {x[0]*y[1]+y[0],x[1]*y[1],x[2]+x[1]*y[2],x[3]+y[3]+x[0]*y[2]};
}
};
const M I={0,1,0,0};
M xort(){return {1,-1,0,0};}
M tim(){return {0,1,1,0};}
struct Segment_Tree{
struct Segment_Tree_node{
ll S,PS,L;M tag;
friend Segment_Tree_node operator+(Segment_Tree_node x,Segment_Tree_node y){return {x.S+y.S,x.PS+y.PS,x.L+y.L,I};}
friend Segment_Tree_node operator*(Segment_Tree_node x,M y){return {x.S*y[1]+x.L*y[0],x.PS+x.S*y[2]+x.L*y[3],x.L,x.tag*y};}
}t[N<<2];
inline int ls(int x){return x<<1;}
inline int rs(int x){return x<<1|1;}
#define mid ((l+r)>>1)
inline void f(int p,M x){t[p]=t[p]*x;}
inline void push_up(int p){t[p]=t[ls(p)]+t[rs(p)];}
inline void push_down(int p){f(ls(p),t[p].tag),f(rs(p),t[p].tag),t[p].tag=I;}
void build(int p,int l,int r){
if(l==r) return t[p]={0,0,1,I},void();
else{
build(ls(p),l,mid),build(rs(p),mid+1,r);
push_up(p);
}
}
void change(int p,int l,int r,int re_l,int re_r,int op){
if(re_l<=l&&r<=re_r) return f(p,op?tim():xort());
else{
push_down(p);
if(re_l<=mid) change(ls(p),l,mid,re_l,re_r,op);
if(mid<re_r) change(rs(p),mid+1,r,re_l,re_r,op);
push_up(p);
}
}
ll query(int p,int l,int r,int re_l,int re_r){
if(re_l<=l&&r<=re_r) return t[p].PS;
else if(!(r<re_l||l>re_r)) return push_down(p),query(ls(p),l,mid,re_l,re_r)+query(rs(p),mid+1,r,re_l,re_r);
else return 0;
}
}T;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>m;
for(int i=1,l,r;i<=m;i++) cin>>l>>r,q[r].push_back({l,i});
T.build(1,1,n);
for(int i=1;i<=n;i++){
T.change(1,1,n,lst[a[i]]+1,i,0);
T.change(1,1,n,1,i,1);
for(auto it:q[i]) ans[it.se]=T.query(1,1,n,it.fi,i);
lst[a[i]]=i;
}
for(int i=1;i<=m;i++) cout<<ans[i]<<"\n";
}

浙公网安备 33010602011771号