2024 Noip 做题记录(二)
Round #5 - 2024.9.14
A. [P9906] Cover
题目大意
给定长度为
的序列,从一个点出发,每次向左或向右一步,共走 步,每个位置上显示最后一次被经过的时刻,求能生成多少合法序列。 数据范围:
。
思路分析
注意到能到达的点一定是一段区间,可以倒序 dp,设
那么最后这一步一定在这个长度为
但是我们可以走来回,因此
答案就是
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5005,MOD=1e9+7;
ll f[MAXN],g[MAXN];
inline void add(ll &x,ll y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
signed main() {
int n,k;
scanf("%d%d",&n,&k);
ll ans=0; f[n]=1;
for(int i=1;i<k;++i) {
memset(g,0,sizeof(g));
for(int j=n;j>=1;--j) {
add(g[j-1],f[j]);
if(j>=i) add(g[j-i],f[j]);
}
memcpy(f,g,sizeof(f));
for(int j=n;j>=1;--j) {
if(i>1) add(f[j],f[j+2]);
ans=(ans+f[j]*(k-i))%MOD;
}
}
printf("%lld\n",ans);
return 0;
}
B. [P10829] Tuple
题目大意
定义一个图是好的,当且仅当图上恰有一个点,或可以由三个大小相等的好图各选出一个点连出三元环得到。
给定一个
个点 条边的无向图,判定该图是否是好的。 数据范围:
。
思路分析
考虑如何刻画好的图,在无向图上不好处理问题,注意到这张图是边仙人掌,可以建出圆方树。
那么原图上的每个环对应一个方点,最特殊的显然是最后一次加入的环,即某个方点删去后整棵树变成大小相同的三部分,且每部分都是好的。
那么这个方点显然就是圆方树的重心,容易证明一张图是好的当且仅当其圆方树的点分树是完美三叉树。
实现的时候可以在建圆方树时直接判断每个边双联通分量大小是否为
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int n,m,tot,dfn[MAXN],low[MAXN],dcnt,stk[MAXN],tp;
vector <int> G[MAXN],E[MAXN];
void link(int u,int v) { E[u].push_back(v),E[v].push_back(u); }
void tarjan(int u) {
dfn[u]=low[u]=++dcnt,stk[++tp]=u;
for(int v:G[u]) {
if(!dfn[v]) {
tarjan(v),low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]) {
int k,c=1; link(u,++tot);
do ++c,link(k=stk[tp--],tot); while(k^v);
if(c!=3) puts("ne"),exit(0);
}
} else low[u]=min(low[u],dfn[v]);
}
}
int qk(int x) {
int c=0;
for(;x>1;x/=3,++c) if(x%3) puts("ne"),exit(0);
return c;
}
int siz[MAXN],cur[MAXN];
bool vis[MAXN];
bool dfs1(int u,int k) {
int cnt=0; vis[u]=true;
for(int v:E[u]) cnt+=!vis[v];
if(cnt!=(k?3:0)) puts("ne"),exit(0);
function<void(int,int)> dfs2=[&](int x,int fz) {
siz[x]=1;
for(int y:E[x]) if(!vis[y]&&y!=fz) dfs2(y,x),siz[x]+=siz[y];
};
dfs2(u,0);
for(int v:E[u]) if(!vis[v]) {
int rt=0,mx=siz[v];
function<void(int,int)> dfs3=[&](int x,int fz) {
cur[x]=mx-siz[x];
for(int y:E[x]) if(!vis[y]&&y!=fz) {
dfs3(y,x),cur[x]=max(cur[x],siz[y]);
}
if(!rt||cur[x]<cur[rt]) rt=x;
};
dfs3(v,u);
if(!dfs1(rt,k-1)) puts("ne"),exit(0);
}
return true;
}
signed main() {
scanf("%d%d",&n,&m),tot=n;
for(int i=1,u,v;i<=m;++i) {
scanf("%d%d",&u,&v);
G[u].push_back(v),G[v].push_back(u);
}
for(int i=1;i<=n;++i) {
sort(G[i].begin(),G[i].end());
if(unique(G[i].begin(),G[i].end())!=G[i].end()) return puts("ne"),0;
}
tarjan(1);
for(int i=1;i<=n;++i) if(!dfn[i]) return puts("ne"),0;
int rt=0,mx=tot;
function<void(int,int)> dfs3=[&](int x,int fz) {
siz[x]=1;
for(int y:E[x]) if(!vis[y]&&y!=fz) {
dfs3(y,x),cur[x]=max(cur[x],siz[y]),siz[x]+=siz[y];
}
cur[x]=max(cur[x],mx-siz[x]);
if(!rt||cur[x]<cur[rt]) rt=x;
};
dfs3(1,0);
puts(dfs1(rt,qk(n))?"da":"ne");
return 0;
}
C. [P10822] Subset
题目大意
给定
, 次询问 有多少子区间本质不同颜色数为奇数。 数据范围:
。
思路分析
设
维护奇偶性,我们要支持区间反转区间历史和。
类似区间加区间历史和,构造一个历史和标记,每个线段树节点维护:每次打历史标记时,当前节点的懒标记是处于反转还是未反转状态,对这两种情况分别记录次数即可。
信息合并和标记下传都是容易的。
时间复杂度:
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
int n,q;
struct SegmentTree {
int sum[MAXN<<2],len[MAXN<<2];
bool tg[MAXN<<2];
int t0[MAXN<<2],t1[MAXN<<2];
ll hsum[MAXN<<2];
void adt(int p,int z0,int z1,int rv) {
hsum[p]+=1ll*z0*sum[p]+1ll*z1*(len[p]-sum[p]);
if(tg[p]) t0[p]+=z1,t1[p]+=z0;
else t0[p]+=z0,t1[p]+=z1;
if(rv) tg[p]^=1,sum[p]=len[p]-sum[p];
}
void psd(int p) {
adt(p<<1,t0[p],t1[p],tg[p]),adt(p<<1|1,t0[p],t1[p],tg[p]),t0[p]=t1[p]=tg[p]=0;
}
void psu(int p) { sum[p]=sum[p<<1]+sum[p<<1|1],hsum[p]=hsum[p<<1]+hsum[p<<1|1]; }
void init(int l=1,int r=n,int p=1) {
len[p]=r-l+1;
if(l==r) return ;
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
}
void upd(int ul,int ur,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return adt(p,0,0,1),void();
int mid=(l+r)>>1; psd(p);
if(ul<=mid) upd(ul,ur,l,mid,p<<1);
if(mid<ur) upd(ul,ur,mid+1,r,p<<1|1);
psu(p);
}
ll qry(int ul,int ur,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return hsum[p];
int mid=(l+r)>>1; psd(p);
if(ur<=mid) return qry(ul,ur,l,mid,p<<1);
if(mid<ul) return qry(ul,ur,mid+1,r,p<<1|1);
return qry(ul,ur,l,mid,p<<1)+qry(ul,ur,mid+1,r,p<<1|1);
}
} T;
ll ans[MAXN];
int a[MAXN],p[MAXN];
vector <array<int,2>> Q[MAXN];
signed main() {
ios::sync_with_stdio(false);
cin>>n,T.init();
for(int i=1;i<=n;++i) cin>>a[i];
cin>>q;
for(int i=1,l,r;i<=q;++i) cin>>l>>r,Q[r].push_back({l,i});
for(int i=1;i<=n;++i) {
T.upd(p[a[i]]+1,i),p[a[i]]=i,T.adt(1,1,0,0);
for(auto z:Q[i]) ans[z[1]]=T.qry(z[0],i);
}
for(int i=1;i<=q;++i) cout<<ans[i]<<"\n";
return 0;
}
*D. [P10882] Triangle
题目大意
给定
, 次询问 ,找出一组 满足存在三角形以 为三边边长,并最小化 。 数据范围:
。
思路分析
考虑对值域倍增分块。
对于每个块
对每个
我们发现,对于
我们考虑最终的三角形中有多少个元素
如果有
因此三角形最长的两条边排序后一定是相邻的,因此最长边的范围也在
那么我们只要考虑
注意到
实现代码的时候我们可以按
这样的数如果
这部分总的复杂度为
然后考虑
不妨设这个元素为
设
因此对每个
对每个块
因此所有
对于每个
那么现在我们只需要将支配对数量优化到一个可以接受的量级,我们设
如果
考虑插入
然后考虑所有
因此只有
然后考虑
还是考虑支配对,不妨设
那么如果有两个
因此支配对一定是
这部分支配对总数和区间长度依然成线性。
因此所有支配对数量的总和是
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
typedef array<int,3> info;
const int MAXN=2.5e5+5,MAXQ=5e5+5,MAXV=1e7+5,inf=1e9;
const void chkmin(int &x,const int &y) { x=x<y?x:y; }
info operator +(info f,info g) {
for(int x:f) for(int &y:g) if(y>x) swap(x,y);
return g;
}
int n,q,a[MAXN],bl[MAXN],ql[MAXQ],qr[MAXQ],ans[MAXQ],cnt[MAXN],pre[MAXN],nxt[MAXN];
bool vis[MAXQ];
vector <int> arr[MAXQ];
struct zkwSegt {
static const int N=1<<18;
info tr[N<<1];
void init(int k) {
for(int i=0;i<N;++i) tr[i+N].fill(inf);
for(int i=1;i<=n;++i) if(bl[i]==k) tr[i+N][0]=a[i];
for(int i=N-1;i;--i) tr[i]=tr[i<<1]+tr[i<<1|1];
}
info qry(int l,int r) {
info s{inf,inf,inf};
for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) {
if(~l&1) s=s+tr[l^1];
if(r&1) s=s+tr[r^1];
}
return s;
}
} S;
void calc(int &s,const vector<int>&w) {
int k=w.size(),i=1;
while(i+1<k&&w[i-1]+w[i]<=w[i+1]) ++i;
if(i+1>=k) return ;
int j=i-1,lst=w[i+1]-w[i];
while(j>0&&w[j-1]>lst) --j;
chkmin(s,w[j]+w[i]+w[i+1]);
for(++i;i+1<k;++i) if(w[i+1]-w[i]<lst) {
for(lst=w[i+1]-w[i];j>0&&w[j-1]>lst;--j);
chkmin(s,w[j]+w[i]+w[i+1]);
}
}
vector <array<int,2>> M[MAXN],Q[MAXN];
void ins(int x,int y,int z) { M[max({x,y,z})].push_back({min({x,y,z}),a[x]+a[y]+a[z]}); }
vector <int> stk[MAXV];
int b[MAXN],pos[MAXV];
void gen(int id,int l,int r) {
int x=a[id];
for(int i=l;i<=r;++i) if(a[i]>x) b[i]=a[i]/x,stk[b[i]].clear();
for(int i=l;i<=r;++i) if(a[i]>x) {
vector<int>&s=stk[b[i]];
while(s.size()&&a[s.back()]>=a[i]) ins(s.back(),id,i),s.pop_back();
if(s.size()) ins(s.back(),id,i);
s.push_back(i);
}
for(int i=l;i<=r;++i) if(a[i]>x) pos[b[i]]=pos[b[i]-1]=0;
for(int i=l;i<=r;++i) if(a[i]>x) {
int j=pos[b[i]-1];
if(j&&x+a[j]>a[i]) ins(id,j,i);
pos[b[i]]=i;
}
for(int i=l;i<=r;++i) if(a[i]>x) pos[b[i]]=pos[b[i]-1]=0;
for(int i=r;i>=l;--i) if(a[i]>x) {
int j=pos[b[i]-1];
if(j&&x+a[j]>a[i]) ins(id,j,i);
pos[b[i]]=i;
}
}
struct FenwickTree {
int tr[MAXN],s;
void init() { fill(tr,tr+n+1,inf); }
void upd(int x,int v) { for(;x;x&=x-1) tr[x]=min(tr[x],v); }
int qry(int x) { for(s=inf;x<=n;x+=x&-x) s=min(s,tr[x]); return s; }
} T;
signed main() {
ios::sync_with_stdio(false);
cin>>n>>q;
for(int i=1;i<=n;++i) cin>>a[i],bl[i]=__lg(a[i]);
for(int i=1;i<=q;++i) cin>>ql[i]>>qr[i],ans[i]=inf;
for(int k=0;k<24;++k) {
vector <int> idx;
S.init(k),memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;++i) if(bl[i]==k) cnt[i]=1,idx.push_back(i);;
for(int i=1;i<=n;++i) pre[i]=(bl[i]==k?i:pre[i-1]);
for(int i=n;i>=1;--i) nxt[i]=(bl[i]==k?i:nxt[i+1]);
for(int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
for(int i=1;i<=q;++i) if(!vis[i]) {
int w=cnt[qr[i]]-cnt[ql[i]-1];
if(w<3) {
if(w>1) {
int x=nxt[ql[i]],y=pre[qr[i]];
if(a[x]>a[y]) swap(x,y);
arr[i].push_back(a[x]),arr[i].push_back(a[y]);
} else if(w) arr[i].push_back(a[nxt[ql[i]]]);
} else {
info z=S.qry(ql[i],qr[i]);
arr[i].push_back(z[0]),vis[i]=true;
chkmin(ans[i],z[0]+z[1]+z[2]);
}
}
int m=idx.size();
for(int i=0;i<m;++i) gen(idx[i],i>1?idx[i-2]+1:1,i+2<m?idx[i+2]-1:n);
}
for(int i=1;i<=q;++i) calc(ans[i],arr[i]),Q[qr[i]].push_back({ql[i],i});
T.init();
for(int i=1;i<=n;++i) {
for(auto z:M[i]) T.upd(z[0],z[1]);
for(auto z:Q[i]) chkmin(ans[z[1]],T.qry(z[0]));
}
for(int i=1;i<=q;++i) {
if(ans[i]==inf) cout<<"yumi!\n";
else cout<<ans[i]<<"\n";
}
return 0;
}
Round #6 - 2024.9.15
A. [P10884] Increase
题目大意
给定
个元素,每个元素有高度和权值,求有多少个高度单调不降的子序列满足元素权值和 。 数据范围:
。
思路分析
考虑折半搜索,从前往后搜出在
那么查询答案相当于在
可以离线下来二维数点,但直接暴力排序二分也能通过。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=45,inf=1e9;
int n,m,a[MAXN],w[MAXN];
ll k,ans=0;
vector <ll> s[MAXN];
void dfs1(int i,int x,ll c) {
if(i>n/2) return s[x].push_back(c);
dfs1(i+1,x,c);
if(a[i]>=a[x]) dfs1(i+1,i,c+w[i]);
}
void dfs2(int i,int x,ll c) {
if(i<=n/2) {
for(int j=0;j<=n/2;++j) if(a[j]<=a[x]) {
ans+=s[j].end()-lower_bound(s[j].begin(),s[j].end(),k-c);
}
return ;
}
dfs2(i-1,x,c);
if(a[i]<=a[x]) dfs2(i-1,i,c+w[i]);
}
signed main() {
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;++i) scanf("%d%d",&a[i],&w[i]);
dfs1(1,0,0);
for(int i=1;i<=n/2;++i) sort(s[i].begin(),s[i].end());
a[n+1]=inf;
dfs2(n,n+1,0);
printf("%lld\n",ans);
return 0;
}
B. [P10240] Backpack
题目大意
给定
个元素,每个元素有权重 ,进行若干轮操作,每次选出最多的元素使得 ,多种方案选字典序最大一组方案,选出的元素都删除,求多少轮后所有元素被删空。 数据范围:
。
思路分析
首先考虑怎么选出最多元素,显然会按从小到大的顺序贪心取出前
然后考虑怎么确定一组解,可以逐位贪心,即先最大化标号最小元素的位置,可以二分一个
由于我们要动态删除元素,因此可以树状数组套值域线段树树,求出一组解的复杂度
由于
从小到大贪心求 std::multiset
维护。
时间复杂度:
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=50005;
const ll inf=1e18;
int n,m,a[MAXN],id[MAXN],rk[MAXN],vals[MAXN];
struct Segt {
static const int MAXS=MAXN*200;
int ls[MAXS],rs[MAXS],siz[MAXS],tot;
ll sum[MAXS];
void ins(int u,int op,int l,int r,int &p) {
if(!p) p=++tot;
siz[p]+=op,sum[p]+=op*vals[u];
if(l==r) return ;
int mid=(l+r)>>1;
if(u<=mid) ins(u,op,l,mid,ls[p]);
else ins(u,op,mid+1,r,rs[p]);
}
ll qry(int k,int l,int r,vector<int>&P) {
if(l==r) return vals[l];
int mid=(l+r)>>1,c=0;
for(int p:P) c+=siz[ls[p]];
if(k<=c) {
for(int&p:P) p=ls[p];
return qry(k,l,mid,P);
} else {
ll s=0;
for(int&p:P) s+=sum[ls[p]],p=rs[p];
return qry(k-c,mid+1,r,P)+s;
}
}
int rt[MAXN];
void ins(int x,int u,int op) { for(;x;x&=x-1) ins(u,op,1,n,rt[x]); }
ll qry(int k,int x) {
int s=0; vector <int> P;
for(;x<=n;x+=x&-x) s+=siz[rt[x]],P.push_back(rt[x]);
if(s<k) return inf;
return qry(k,1,n,P);
}
} T;
multiset <int> A;
int solve() {
int s=0,c=0;
for(int i:A) {
if(s+i>m) return c;
s+=i,++c;
}
return c;
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),id[i]=i;
sort(id+1,id+n+1,[&](int x,int y){ return a[x]<a[y]; });
for(int i=1;i<=n;++i) rk[id[i]]=i,vals[i]=a[id[i]];
for(int i=1;i<=n;++i) A.insert(a[i]),T.ins(i,rk[i],1);
int cnt=0;
for(;A.size();++cnt) {
int k=solve(),rem=m;
for(int i=1;i<=k;++i) {
int l=1,r=n,p=0;
while(l<=r) {
int mid=(l+r)>>1;
ll z=T.qry(k-i+1,mid);
if(z<=rem) p=mid,l=mid+1;
else r=mid-1;
}
A.erase(A.find(a[p])),T.ins(p,rk[p],-1),rem-=a[p];
}
}
printf("%d\n",cnt);
return 0;
}
C. [P10241] Subpath
题目大意
给定
个点的树,点有权值,求出路径上最长严格递增子序列的长度。 数据范围:
。
思路分析
先考虑如何求
可以考虑线段树合并,对每个
然后考虑如何在路径
假如
如果
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int ans=0;
struct SegmentTree {
int tot,ls[MAXN*32],rs[MAXN*32],f[MAXN*32],g[MAXN*32];
void ins(int u,int x,int y,int l,int r,int &p) {
if(!p) p=++tot;
f[p]=max(f[p],x),g[p]=max(g[p],y);
if(l==r) return ;
int mid=(l+r)>>1;
u<=mid?ins(u,x,y,l,mid,ls[p]):ins(u,x,y,mid+1,r,rs[p]);
}
void merge(int l,int r,int q,int &p) {
if(!q||!p) return p|=q,void();
f[p]=max(f[p],f[q]),g[p]=max(g[p],g[q]);
if(l==r) return ;
ans=max({ans,f[ls[p]]+g[rs[q]],f[ls[q]]+g[rs[p]]});
int mid=(l+r)>>1;
merge(l,mid,ls[q],ls[p]),merge(mid+1,r,rs[q],rs[p]);
}
int qry(int ul,int ur,int op,int l,int r,int p) {
if(ul<=l&&r<=ur) return op?g[p]:f[p];
int mid=(l+r)>>1,s=0;
if(ul<=mid) s=max(s,qry(ul,ur,op,l,mid,ls[p]));
if(mid<ur) s=max(s,qry(ul,ur,op,mid+1,r,rs[p]));
return s;
}
} T;
vector <int> G[MAXN];
int n,f[MAXN],g[MAXN],a[MAXN],rt[MAXN],V=1e9+1;
void dfs(int u,int fz) {
f[u]=g[u]=1;
for(int v:G[u]) if(v^fz) {
dfs(v,u);
int tf=T.qry(0,a[u]-1,0,0,V,rt[v])+1;
int tg=T.qry(a[u]+1,V,1,0,V,rt[v])+1;
ans=max({ans,tf+T.qry(a[u]+1,V,1,0,V,rt[u]),T.qry(0,a[u]-1,0,0,V,rt[u])+tg});
f[u]=max(f[u],tf),g[u]=max(g[u],tg);
T.merge(0,V,rt[v],rt[u]);
}
T.ins(a[u],f[u],g[u],0,V,rt[u]);
}
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1,u,v;i<n;++i) {
scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
}
dfs(1,0);
ans=max({ans,T.f[rt[1]],T.g[rt[1]]});
printf("%d\n",ans);
return 0;
}
*D. [P8340] Express
题目大意
求
有多少个子集能用其子集和表出 中所有数。 数据范围:
。
思路分析
首先考虑如何求出一个集合中最小不可表示的元素,这是经典问题,找到排序后第一个
因此我们可以考虑容斥,枚举
先考虑如何算出总方案数
由于每个元素都不能相同,因此子集大小至多是
我们考虑把一个方案对应成一个杨表,即从大到小排列,第
那么这个杨表一共有
从大到小枚举列高
然后考虑如何容斥,即我们要钦定所有
那么对于
考虑类 CDQ 分治的过程维护贡献,但我们发现
因此 CDQ 分治时
因此我们只要考虑
维护在
枚举
最后
最终的答案是
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5,B=1000;
int MOD,f[MAXN],g[MAXN],pw[MAXN];
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
void dp(int n) {
if(n==1) return ;
dp(n>>1),memset(g,0,sizeof(g));
for(int i=B;i;--i) {
for(int j=0;j+(j+1)*i<=n;++j) add(g[j+(j+1)*i],f[j]);
for(int j=n;~j;--j) g[j]=(j>=i?g[j-i]:0);
for(int j=i;j<=n;++j) add(g[j],g[j-i]);
}
for(int j=n/2+1;j<=n;++j) if(g[j]) add(f[j],MOD-g[j]);
}
signed main() {
int n;
scanf("%d%d",&n,&MOD);
for(int i=pw[0]=1;i<=n;++i) pw[i]=pw[i-1]*2%MOD;
for(int i=B;i;--i) {
for(int j=n;j>=i;--j) f[j]=f[j-i];
f[i]=1;
for(int j=i;j<=n;++j) add(f[j],f[j-i]);
}
f[0]=1,dp(n);
int ans=pw[n];
for(int i=0;i<n;++i) ans=(ans-1ll*f[i]*pw[n-i-1])%MOD;
printf("%d\n",(ans+MOD)%MOD);
return 0;
}
E. [P10103] Permutation
题目大意
组询问给定 ,求有多少 阶排列 满足 且所有 。 数据范围:
。
思路分析
先在
因此可以设计状态 dp:
考虑分别消去一个有限制的元素和一个无限制的元素,根据组合意义处理。
先考虑一个无限制的元素如何填:
- 如果填在自己对应的位置上,那么转移到
。 - 否则相当于这个元素被钦定了“不能放在自己位置上”的限制,变成一个有限制的元素,转移到
。
因此
然后考虑一个有限制的元素如何填:
- 填在一个无限制元素对应的位置上,那么这个无限制元素依然可以任意填,转移到
。 - 填在一个有限制元素对应的位置上,变成经典错拍,考虑这个元素是否和当前元素互换位置,转移到
。
因此
那么考虑对
然后对于一个
时间复杂度
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
namespace FastMod {
typedef unsigned long long ull;
typedef __uint128_t uLL;
ull b,m;
inline void init(ull B) { b=B,m=ull((uLL(1)<<64)/B); }
inline ull mod(ull a) {
ull q=((uLL(m)*a)>>64),r=a-q*b;
return r>=b?r-b:r;
}
};
#define o FastMod::mod
const int MAXN=2e5+5,B=450,MOD=998244353,N=2e5;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=o(a*a),b>>=1) if(b&1) s=o(s*a); return s; }
ll fac[MAXN],ifac[MAXN],f[MAXN],C[B+5][B+5],ans[MAXN];
vector <array<int,3>> Q[MAXN];
signed main() {
FastMod::init(MOD);
for(int i=fac[0]=ifac[0]=1;i<=N;++i) ifac[i]=ksm(fac[i]=o(fac[i-1]*i));
int q; scanf("%d",&q);
for(int i=1,n,m;i<=q;++i) {
scanf("%d%d",&n,&m);
if(n-m<m) ans[i]=0;
else ans[i]=o(fac[n-m]*ifac[n-2*m]),Q[m/B].push_back({n-2*m,m,i});
}
for(int i=0;i<=B;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=o(C[i-1][j]+C[i-1][j-1]);
for(int x=0;x*B<=N;++x) if(Q[x].size()) {
int M=x*B;
f[0]=fac[M],f[1]=o(M*fac[M]);
for(int n=2;n<=N;++n) f[n]=o((n+M-1)*f[n-1]+(n-1)*f[n-2]);
for(auto z:Q[x]) {
int n=z[0],m=z[1],k=m-M; ll s=0;
for(int i=0;i<=k;++i) s=o(s+f[n+i]*C[k][i]);
ans[z[2]]=o(ans[z[2]]*s)%MOD;
}
}
for(int i=1;i<=q;++i) printf("%lld\n",ans[i]);
return 0;
}
*F. [P10107] Distance
题目大意
给定
个点的有根树,点有点权, 次询问 ,求出所有 子树内距离 不超过 的点 , 的和。 数据范围:
。
思路分析
考虑刻画子问题,注意到这个问题的子问题不太可能被一个子树内的信息描述,而是形如同层内一些节点的子树信息的合并。
又因为这题要处理的信息和距离的二进制异或有关,这启示我们用倍增一类自带二进制结构的算法刻画信息。
因此我们可以考虑
那么
由于
那么我们要计数
如果暴力设
考虑优化,很显然
记
那么原本的
那么
那么不难得到转移:
查询答案可以做一些类似的过程,每次考虑最高位的影响,实际上每个最高位都只会影响深度的一段后缀,也可以用
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
vector <int> G[MAXN];
int n,q,a[MAXN],lst[MAXN],dep[MAXN],pre[MAXN],dw[MAXN][20],g[MAXN][20];
ll f[MAXN][20];
void dfs(int u,int fz) {
dep[u]=dep[fz]+1,pre[u]=lst[dep[u]],lst[dep[u]]=u;
dw[u][0]=dw[pre[u]][0];
for(int v:G[u]) if(v^fz) dfs(v,u),dw[u][0]=v;
for(int k=1;k<20;++k) dw[u][k]=dw[dw[u][k-1]][k-1];
for(int k=0;k<20;++k) {
g[u][k]=g[dw[u][0]][k]+g[pre[u]][k]-g[dw[pre[u]][0]][k]+(a[u]>>k&1?-1:1);
}
f[u][0]=a[u]+f[pre[u]][0];
for(int k=1;k<20;++k) {
int v=dw[u][k-1];
f[u][k]=f[u][k-1]+f[v][k-1]+(1ll<<(k-1))*(g[v][k-1]-g[dw[u][k]][k-1]);
}
}
ll qf(int u,int z) {
int v=u; ll s=0; ++z;
for(int k=19;~k;--k) if(z>>k&1) v=dw[v][k];
for(int k=19;~k;--k) if(z>>k&1) {
s+=f[u][k]+(1ll<<k)*(g[dw[u][k]][k]-g[v][k]),u=dw[u][k];
}
return s;
}
signed main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
for(int u=2,v;u<=n;++u) cin>>v,G[u].push_back(v),G[v].push_back(u);
dfs(1,0),cin>>q;
for(int x,k;q--;) cin>>x>>k,cout<<qf(x,k)-qf(pre[x],k)<<"\n";
return 0;
}
Round #7 - 2024.9.16
A. [P10812] Factor
题目大意
给定
数轴,每个位置至多经过一次,不可超出 ,每次可以从 走到 或 的因子,求有多少 的路径。 数据范围:
。
思路分析
如果不存在
如果存在这种操作,我们就要将若干操作合并,我们以每次
即
那么转移要么直接向
我们可以对所有
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005;
vector <int> fac[MAXN];
int n,MOD,f[MAXN][MAXN];
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
signed main() {
scanf("%d%d",&n,&MOD);
for(int i=1;i<=n;++i) for(int j=i;j<=n;j+=i) fac[j].push_back(i);
f[n][n+1]=1;
for(int i=n;i>1;--i) {
int s=0;
for(int j=n;j>=i;--j) {
add(s,f[i][j+1]);
for(int k:fac[j]) if(k<i) add(f[k][i],s);
}
add(f[i-1][i],s);
}
int s=0;
for(int i=1;i<=n+1;++i) add(s,f[1][i]);
printf("%d\n",s);
return 0;
}
B. [P10674] Subgraph
题目大意
给定
个点 条边的无向图,求有多少点集对 使得每个 中元素都在某两个 中元素的某条简单路径上。 数据范围:
。
思路分析
对每个
考虑刻画
考虑在方点处统计权值,对于一个大小为
那么对于斯坦纳树的根,如果其是圆点,那么没有将这个点考虑在
dp 时设
但是我们在统计点
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
ll dp[MAXN],ans,pw[MAXN];
vector <int> G[MAXN],E[MAXN];
void link(int x,int y) { E[x].push_back(y),E[y].push_back(x); }
int n,m,k,low[MAXN],dfn[MAXN],dcnt,stk[MAXN],tp,w[MAXN];
bool ins[MAXN];
void tarjan(int u) {
ins[stk[++tp]=u]=true,dfn[u]=low[u]=++dcnt;
for(int v:G[u]) {
if(!dfn[v]) {
tarjan(v),low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]) {
link(++k,u);
while(ins[v]) link(stk[tp],k),++w[k],ins[stk[tp--]]=false;
}
} else low[u]=min(low[u],dfn[v]);
}
}
void dfs(int u,int fz) {
if(E[u].size()==1&&fz!=0) return ++ans,dp[u]=1,void();
ll g[3]={1,0,0};
for(int v:E[u]) if(v^fz) {
dfs(v,u),g[2]=(g[2]*(dp[v]+1)+g[1]*dp[v])%MOD,g[1]=(g[1]+g[0]*dp[v])%MOD;
}
if(u>n) ans=(ans+g[2]*pw[w[u]])%MOD,dp[u]=(g[2]+g[1])*pw[w[u]]%MOD;
else ans=(ans+2*g[2]+g[1]+g[0])%MOD,dp[u]=(2*g[2]+2*g[1]+g[0])%MOD;
}
signed main() {
scanf("%d%d",&n,&m),k=n;
for(int i=pw[0]=1;i<=n;++i) pw[i]=pw[i-1]*2%MOD;
for(int i=1,u,v;i<=m;++i) {
scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
}
tarjan(1),dfs(1,0);
printf("%lld\n",(2*ans+1)%MOD);
return 0;
}
C. [P10682] Xor
题目大意
给定
个点 条边的 DAG,求有多少种给每条边赋 权值的方式使得任意两条起终点相同的路径权值和 都同余。 数据范围:
。
思路分析
很显然题目的要求就是对于任意两条
由于本题全填
那么我们只要优化限制组数即可。
对每个起点
即对于一条非树边
对于任意一条路径,找到其中的第一条非树边
那么此时总共只有 bitset
优化高斯消元维护。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=405,MOD=1e9+7;
int n,m;
struct Edge { int v,id; };
vector <Edge> G[MAXN];
bool vis[MAXN];
bitset <MAXN> w,d[MAXN],x[MAXN];
void ins() {
for(int i=1;i<=m;++i) if(w[i]) {
if(!x[i].any()) return x[i]=w,void();
else w^=x[i];
}
}
void dfs(int u) {
vis[u]=true;
for(auto e:G[u]) {
if(!vis[e.v]) d[e.v]=d[u],d[e.v].set(e.id),dfs(e.v);
else w=d[u],w^=d[e.v],w.flip(e.id),ins();
}
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),G[u].push_back({v,i});
for(int i=1;i<=n;++i) {
for(int u=1;u<=n;++u) d[u].reset(),vis[u]=false;
dfs(i);
}
int ans=1;
for(int i=1;i<=m;++i) if(!x[i].any()) ans=ans*2%MOD;
printf("%d\n",ans);
return 0;
}
*D. [P11051] Range
题目大意
给定
个点的树,每个点有点权 , 次询问 ,构造一组 使得每个子树内 的和都在 之间,最小化 。 数据范围;
。
思路分析
先从
自下而上地开始填
观察根节点处的变化量,设原有
不难证明这个界是可以取到的,可以每个
然后考虑
这相当于把每个
设有
然后考虑一般的情况,由于我们已经会解决
对每个
依然考虑维护
首先我们肯定转成倒序插入节点,可以用并查集维护产生和删除的每个连通块。
并且可以考虑差分,即一个连通块在插入
那么这样就可以维护出所有
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
vector <int> G[MAXN];
int n,w[MAXN],fa[MAXN],dsu[MAXN],siz[MAXN];
bool vis[MAXN];
ll cnt[MAXN],s1[MAXN],s2[MAXN],clf;
int find(int x) { return x^dsu[x]?dsu[x]=find(dsu[x]):x; }
void add(int x,int c,int v) { cnt[siz[x]]-=v,cnt[siz[x]+=c]+=v; }
void merge(int x,int y,int v) { dsu[y]=x,cnt[siz[y]]-=v,add(x,siz[y],v); }
void ins(int x) {
vis[x]=true;
if(fa[x]&&vis[fa[x]]) add(find(fa[x]),-1,w[x]);
for(int y:G[x]) {
if(!vis[y]) add(x,1,w[x]);
else merge(x,y,w[x]);
}
if(vis[fa[x]]) merge(find(fa[x]),x,w[x]);
}
void init(vector <int> P,vector <int> W) {
n=W.size(); vector <int> ord;
for(int i=1;i<=n;++i) w[i]=W[i-1],ord.push_back(i),dsu[i]=i;
for(int i=2;i<=n;++i) G[fa[i]=P[i-1]+1].push_back(i);
for(int i=1;i<=n;++i) if(G[i].empty()) G[i].push_back(0),clf+=w[i];
sort(ord.begin(),ord.end(),[&](int x,int y){ return w[x]>w[y]; });
for(int u:ord) ins(u);
for(int i=n;i>=1;--i) s1[i]=s1[i+1]+cnt[i],s2[i]=s2[i+1]+cnt[i]*i;
}
ll query(int L,int R) {
int x=min(n,R/L)+1;
return clf*L+s2[x]*L-s1[x]*R;
}
*E. [P11054] Connect
题目大意
给定一张
个点 条边的无向图,交互器中每个点有一个 的颜色。 每次交互时,你可以把若干个点染成
中的任意颜色,交互器会告诉你新图中的同色连通块数量。 请在
次交互之内确定每个点的颜色。 数据范围:
。
思路分析
先从链入手。
将所有奇数下标的点全部染成某种颜色
以此为依据二分,可以求出每个颜色为
然后考虑推广,我们可以对链上所有下标为计数的点一次性检验,那么在图上我们可以对一个独立集状物一次性检验。
具体来说,选定一个独立集
考虑进一步优化,观察我们用到了独立集的什么性质。
首先要求
其次要求每个
这是容易的,取出一棵生成树并黑白染色得到两个集合分别作为
最终我们只要求出每个同色连通块即可,也就是本题
这个不难,考虑增量法构造,依次加入每个点
将
注意到每次二分实际上都减少了一个点(和其他点并成同色连通块,或确定一个连通块的颜色),那么我们在
实际上由于元素数的不断减少,询问次数不超过
注意特判全部点颜色相同的 Corner Case。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
int perform_experiment(vector<int>E);
const int MAXN=255;
vector <int> G[MAXN],E[MAXN],R[MAXN];
int n;
struct DSU {
int dsu[MAXN];
void init() { iota(dsu,dsu+n,0); }
int find(int x) { return x^dsu[x]?dsu[x]=find(dsu[x]):x; }
bool merge(int x,int y) {
x=find(x),y=find(y),dsu[x]=y;
return x^y;
}
} F,T;
int count(const vector<int>&V) {
static bitset<MAXN> inq;
inq.reset(),T.init();
for(int i:V) inq.set(i);
int s=V.size();
for(int i:V) for(int j:G[i]) if(inq[j]) s-=T.merge(i,j);
return s;
}
int col[MAXN];
void solve(vector<int> S) {
for(int c=0;c<n;++c) {
vector <int> X;
while(S.size()) {
auto chk=[&](int k) {
vector <int> q(n,c),r;
for(int i=0;i<=k;++i) for(int u:R[S[i]]) q[u]=-1;
for(int i=0;i<n;++i) if(~q[i]) r.push_back(i);
int z=perform_experiment(q);
return z<count(r)+k+1;
};
int l=0,r=S.size()-2,x=S.size()-1;
if(!chk(x)) {
X.insert(X.end(),S.begin(),S.end());
break;
}
while(l<=r) {
int mid=(l+r)>>1;
if(chk(mid)) x=mid,r=mid-1;
else l=mid+1;
}
col[S[x]]=c;
X.insert(X.end(),S.begin(),S.begin()+x);
S.erase(S.begin(),S.begin()+x+1);
}
S.swap(X);
}
}
vector<int> find_colours(int N,vector<int>X,vector<int>Y) {
n=N,F.init();
for(int i=0;i<(int)X.size();++i) G[X[i]].push_back(Y[i]),G[Y[i]].push_back(X[i]);
for(int u=0;u<n;++u) {
static bitset <MAXN> vis;
vis.reset();
vector <int> Ne,C;
for(int v:G[u]) if(v<u&&!vis[F.find(v)]) {
Ne.push_back(F.find(v)),vis.set(F.find(v));
}
while(Ne.size()) {
auto chk=[&](int k) { //qry Ne[0,k]
vector <int> q(n,n),r;
vis.reset(),q[u]=-1;
for(int i=0;i<=k;++i) vis.set(Ne[i]);
for(int i=0;i<u;++i) if(vis[F.find(i)]) q[i]=-1;
for(int i=0;i<n;++i) if(~q[i]) r.push_back(i);
int z=perform_experiment(q);
return count(r)+k+2>z;
};
int l=0,r=Ne.size()-2,x=Ne.size()-1;
if(!chk(x)) break;
while(l<=r) {
int mid=(l+r)>>1;
if(chk(mid)) x=mid,r=mid-1;
else l=mid+1;
}
C.push_back(Ne[x]);
Ne.erase(Ne.begin(),Ne.begin()+x+1);
}
for(int v:C) F.merge(u,v);
}
vector <int> bl(n);
for(int i=0;i<n;++i) R[bl[i]=F.find(i)].push_back(i);
F.init();
for(int i=0;i<n;++i) for(int j:G[i]) if(F.merge(bl[i],bl[j])) {
E[bl[i]].push_back(bl[j]),E[bl[j]].push_back(bl[i]);
}
vector <int> S[2];
function<void(int,int,int)> dfs=[&](int u,int fz,int c) {
S[c].push_back(u);
for(int v:E[u]) if(v^fz) dfs(v,u,c^1);
};
dfs(bl[0],-1,0);
if(S[1].empty()) {
vector <int> q(n,-1);
for(q[0]=0;q[0]<n;++q[0]) if(perform_experiment(q)==1) {
return vector<int>(n,q[0]);
}
}
solve(S[0]),solve(S[1]);
vector <int> cols(n);
for(int i=0;i<n;++i) cols[i]=col[bl[i]];
return cols;
}
Round #8 - 2024.9.19
A. [P11053] Grid
题目大意
给定
的 01 矩阵 的第一行和第一列,定义 , 次询问 的某个子矩阵的元素和。 数据范围:
。
思路分析
观察这个矩阵,发现如果
并且我们发现如果
此时
因此这种情况只能出现在
对子矩形询问差分成一个以
先求出
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef vector<int> vi;
const int MAXN=2e5+5;
int n,q,a[4][MAXN],b[MAXN][4],cl[MAXN],cr[MAXN];
ll dl[MAXN],dr[MAXN];
bool cmp(array<int,2> i,array<int,2> j) { return i[0]-i[1]<j[0]-j[1]; }
vector<ll> mosaic(vi X,vi Y,vi T,vi B,vi L,vi R) {
n=X.size(),q=T.size();
if(n<=3) {
vector <vi> M(n,vi(n));
M[0]=X;
for(int i=1;i<n;++i) {
M[i][0]=Y[i];
for(int j=1;j<n;++j) M[i][j]=(M[i-1][j]|M[i][j-1])^1;
}
for(int i=0;i<n;++i) for(int j=1;j<n;++j) M[i][j]+=M[i][j-1];
for(int i=1;i<n;++i) for(int j=0;j<n;++j) M[i][j]+=M[i-1][j];
vector <ll> ans(q);
for(int i=0;i<q;++i) {
ans[i]=M[B[i]][R[i]];
if(T[i]) ans[i]-=M[T[i]-1][R[i]];
if(L[i]) ans[i]-=M[B[i]][L[i]-1];
if(T[i]&&L[i]) ans[i]+=M[T[i]-1][L[i]-1];
}
return ans;
}
for(int i=1;i<=n;++i) a[1][i]=X[i-1],b[i][1]=Y[i-1];
a[2][1]=Y[1],a[3][1]=Y[2],b[1][2]=X[1],b[1][3]=X[2];
for(int o:{2,3}) for(int i=2;i<=n;++i) {
a[o][i]=(a[o-1][i]|a[o][i-1])^1;
b[i][o]=(b[i][o-1]|b[i-1][o])^1;
}
vector <array<int,2>> Z;
for(int i=3;i<=n;++i) if(a[3][i]) Z.push_back({3,i}),++cl[i],dl[i]+=i;
for(int i=4;i<=n;++i) if(b[i][3]) Z.push_back({i,3}),++cr[i],dr[i]+=i;
for(int i=n;i>=1;--i) cl[i]+=cl[i+1],dl[i]+=dl[i+1],cr[i]+=cr[i+1],dr[i]+=dr[i+1];
sort(Z.begin(),Z.end(),cmp);
int k=Z.size();
vector <ll> sl(k),sr(k);
if(k) {
sl[0]=Z[0][1];
for(int i=1;i<k;++i) sl[i]=sl[i-1]+Z[i][1];
sr[k-1]=Z[k-1][0];
for(int i=k-2;~i;--i) sr[i]=sr[i+1]+Z[i][0];
}
for(int i=1;i<=3;++i) for(int j=1;j<=n;++j) a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];
for(int i=1;i<=n;++i) for(int j=1;j<=3;++j) b[i][j]+=b[i][j-1]+b[i-1][j]-b[i-1][j-1];
auto qry=[&](int x,int y) -> ll {
if(x<=3) return a[x][y];
if(y<=3) return b[x][y];
ll s=a[3][y]+b[x][3]-a[3][3];
int i=upper_bound(Z.begin(),Z.end(),array<int,2>{x,y},cmp)-Z.begin();
if(i>0) s+=1ll*i*y-sl[i-1];
if(i<k) s+=1ll*(k-i)*x-sr[i];
s+=dl[y]-1ll*y*cl[y];
s+=dr[x]-1ll*x*cr[x];
return s;
};
vector <ll> ans(q);
for(int i=0;i<q;++i) {
ans[i]=qry(B[i]+1,R[i]+1)-qry(T[i],R[i]+1)-qry(B[i]+1,L[i])+qry(T[i],L[i]);
}
return ans;
}
B. [P10303] Function
题目大意
给定
个函数和初值 ,每个函数 形如 ,求一个排列 使得 最大。 数据范围:
。
思路分析
暴力 dp
先考虑
那么我们显然只关心绝对值最大和最小的正数和负数,此时只要对每个
回到
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define LL __int128
using namespace std;
const int V=250;
const LL inf=1e36;
struct info {
LL xl,xr,yl,yr;
bitset <505> q;
info() { xl=0,xr=-inf,yl=inf,yr=0; }
void ins(LL x) {
if(-V<=x&&x<=V) q.set(x+V);
if(x<0) xl=min(xl,x),xr=max(xr,x);
else yl=min(yl,x),yr=max(yr,x);
}
void gen(vector <LL> &v) {
if(xl<=xr) v.push_back(xl),v.push_back(xr);
if(yl<=yr) v.push_back(yl),v.push_back(yr);
for(int i=-V;i<=V;++i) if(q[i+V]) v.push_back(i);
}
} f[1<<15];
int n,o,a[15],b[15],c[15];
LL val(LL x,int i) {
return (x<0?b[i]-a[i]:b[i]+a[i])*x+c[i];
}
void write(LL x) {
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
signed main() {
scanf("%d%d",&n,&o),f[0].ins(o);
for(int i=0;i<n;++i) scanf("%d%d%d",&a[i],&b[i],&c[i]);
for(int s=0;s<(1<<n);++s) {
vector <LL> z; f[s].gen(z);
for(LL x:z) for(int i=0;i<n;++i) if(!(s>>i&1)) {
f[s|1<<i].ins(val(x,i));
}
if(s==(1<<n)-1) write(*max_element(z.begin(),z.end()));
}
puts("");
return 0;
}
C. [P10304] Delete
题目大意
给定一张
个点 条边的 DAG,求出以 为根的 dfs 生成树 , 次询问给定 ,其中 在 中是 的祖先,查询若删 在 上路径的边后, 在 上的子树中有多少个点不能从 出发到达。 数据范围:
。
思路分析
先考虑
初始
然后考虑
离线下来维护
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,m,dep[MAXN],dfn[MAXN],dcnt,st[MAXN][20],deg[MAXN],up[MAXN][20];
vector <int> G[MAXN],E[MAXN],ord,O[MAXN];
void dfs0(int u,int fz) {
dep[u]=dep[fz]+1,dfn[u]=++dcnt,st[dcnt][0]=fz,up[u][0]=fz;
for(int k=1;k<20;++k) up[u][k]=up[up[u][k-1]][k-1];
for(int v:G[u]) {
if(!dfn[v]) E[u].push_back(v),dfs0(v,u);
else O[u].push_back(v);
}
}
int bit(int x) { return 1<<x; }
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int LCA(int x,int y) {
if(x==y) return x;
int l=min(dfn[x],dfn[y])+1,r=max(dfn[x],dfn[y]),k=__lg(r-l+1);
return cmp(st[l][k],st[r-bit(k)+1][k]);
}
int f[MAXN],mn[MAXN][20];
int qry(int x,int r) {
int s=f[r];
for(int k=19;~k;--k) if(dep[up[x][k]]>=dep[r]) s=min(s,mn[x][k]),x=up[x][k];
return s;
}
struct SegmentTree {
int tot,tr[MAXN*20],ls[MAXN*20],rs[MAXN*20];
void ins(int u,int x,int l,int r,int &p) {
if(!p) p=++tot;
tr[p]+=x;
if(l==r) return ;
int mid=(l+r)>>1;
u<=mid?ins(u,x,l,mid,ls[p]):ins(u,x,mid+1,r,rs[p]);
}
void merge(int l,int r,int q,int &p) {
if(!q||!p) return p|=q,void();
tr[p]+=tr[q];
if(l==r) return ;
int mid=(l+r)>>1;
merge(l,mid,ls[q],ls[p]),merge(mid+1,r,rs[q],rs[p]);
}
int qry(int ul,int ur,int l,int r,int p) {
if(ul<=l&&r<=ur) return tr[p];
int mid=(l+r)>>1,s=0;
if(ul<=mid) s+=qry(ul,ur,l,mid,ls[p]);
if(mid<ur) s+=qry(ul,ur,mid+1,r,rs[p]);
return s;
}
void del(int ul,int ur,int l,int r,int &p) {
if(ul<=l&&r<=ur) return tr[p]=0,p=0,void();
int mid=(l+r)>>1;
if(ul<=mid) del(ul,ur,l,mid,ls[p]);
if(mid<ur) del(ul,ur,mid+1,r,rs[p]);
tr[p]=tr[ls[p]]+tr[rs[p]];
}
} T;
int rt[MAXN],ans[MAXN];
vector <array<int,2>> qys[MAXN];
void dfs1(int u) {
T.ins(f[u],1,1,n,rt[u]);
for(int v:E[u]) dfs1(v),T.merge(1,n,rt[v],rt[u]);
if(f[u]<n) {
int w=T.qry(f[u]+1,n,1,n,rt[u]);
T.ins(f[u],w,1,n,rt[u]),T.del(f[u]+1,n,1,n,rt[u]);
}
for(auto z:qys[u]) if(z[0]<n) ans[z[1]]=T.qry(z[0]+1,n,1,n,rt[u]);
}
signed main() {
int q;
scanf("%d%d%d",&n,&m,&q);
for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),G[u].push_back(v),++deg[v];
for(int i=1;i<=n;++i) sort(G[i].begin(),G[i].end());
queue <int> Q;
for(int i=1;i<=n;++i) if(!deg[i]) Q.push(i);
while(Q.size()) {
int u=Q.front(); Q.pop(),ord.push_back(u);
for(int v:G[u]) if(!--deg[v]) Q.push(v);
}
dfs0(1,0);
for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=dcnt;++i) {
st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
}
for(int i=1;i<=n;++i) if(dfn[i]) f[i]=dep[i];
for(int u:ord) if(dfn[u]) {
mn[u][0]=f[u];
for(int k=1;k<20;++k) mn[u][k]=min(mn[u][k-1],mn[up[u][k-1]][k-1]);
for(int v:O[u]) if(dfn[v]) f[v]=min(f[v],qry(u,LCA(u,v)));
}
for(int i=1,a,b;i<=q;++i) scanf("%d%d",&a,&b),qys[b].push_back({dep[a],i});
dfs1(1);
for(int i=1;i<=q;++i) printf("%d\n",ans[i]);
return 0;
}
D. [P8499] Equal
题目大意
给定两棵有根树
,记 ,求是否能在 上删除 个节点使得 同构。 数据范围:
。
思路分析
直接 dp 状态难以接受,考虑自上而下地搜索设
用树哈希判断子树同构,当
我们发现如果有两个子树
那么
可以证明被访问到的
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=1e5+5;
struct Tree {
int n,rt,fa[MAXN],siz[MAXN];
vector <int> E[MAXN];
ull f[MAXN];
ull P(ull x) {
x^=x<<13,x^=x>>7,x^=x<<17;
return x*x*29+x*13+37;
}
void dfs(int u) {
f[u]=siz[u]=1;
for(int v:E[u]) dfs(v),siz[u]+=siz[v],f[u]+=P(f[v]);
sort(E[u].begin(),E[u].end(),[&](int x,int y){ return f[x]<f[y]; });
}
void init() {
cin>>n;
for(int i=1;i<=n;++i) E[i].clear(),siz[i]=f[i]=0;
for(int i=1;i<=n;++i) {
cin>>fa[i];
if(~fa[i]) E[fa[i]].push_back(i);
else rt=i;
}
dfs(rt);
}
} G,H;
int k;
map <array<int,2>,bool> DP;
bool dp(int x,int y) {
if(H.siz[y]==1) return true;
if(DP.count({x,y})) return DP[{x,y}];
if(G.siz[x]<H.siz[y]) return false;
vector <int> &g=G.E[x],&h=H.E[y],s,t;
auto ig=g.begin(),ih=h.begin();
while(ig!=g.end()&&ih!=h.end()) {
if(G.f[*ig]==H.f[*ih]) ++ig,++ih;
else if(G.f[*ig]<H.f[*ih]) s.push_back(*ig++);
else t.push_back(*ih++);
}
s.insert(s.begin(),ig,g.end()),t.insert(t.begin(),ih,h.end());
if(s.size()<t.size()||s.size()>k) return false;
int z=s.size();
vector <int> p(z);
iota(p.begin(),p.end(),0),t.resize(z,0);
do {
bool ok=true;
for(int i=0;i<z;++i) {
ok&=dp(s[i],t[p[i]]);
if(!ok) break;
}
if(ok) return DP[{x,y}]=true;
} while(next_permutation(p.begin(),p.end()));
return DP[{x,y}]=false;
}
void solve() { DP.clear(),G.init(),H.init(),cout<<(dp(G.rt,H.rt)?"Yes\n":"No\n"); }
signed main() {
ios::sync_with_stdio(false);
int T,C; cin>>C>>T>>k;
while(T--) solve();
return 0;
}
E. [P9481] Edge
题目大意
给定
个节点的完全二叉树,每个点向父亲连一条带权有向边,除此之外还有 条从祖先到后代的额外边,求所有点对之间的最短路长度之和。 数据范围:
。
思路分析
考虑把所有边方向取反,此时
对每个祖先求最短路,可以先预处理祖先之间的最短路,都当成边连接,然后把
注意到树高
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=(1<<18)+5,MOD=998244353;
const ll inf=1e18;
struct Edge { int v; ll w; };
vector <Edge> G[MAXN];
int n,m,d[MAXN],to[MAXN];
ll a[MAXN],f[MAXN],dis[MAXN],ans;
bool vis[MAXN];
void dfs(int u) {
if(d[u]==1) return ;
for(int v:{u<<1,u<<1|1}) d[v]=d[u]>>1,dfs(v),f[u]=(f[u]+f[v]+a[v]*d[v])%MOD;
}
void init(int x) {
vis[x]=false,dis[x]=inf;
if(d[x]>1) init(x<<1),init(x<<1|1);
}
void solve(int s) {
for(int x=s;x;x>>=1) to[x>>1]=x,vis[x]=false,dis[x]=inf;
init(s);
priority_queue <array<ll,2>,vector<array<ll,2>>,greater<array<ll,2>>> Q;
Q.push({dis[s]=0,s});
while(Q.size()) {
int u=Q.top()[1]; Q.pop();
if(vis[u]) continue;
vis[u]=true,ans=(ans+dis[u])%MOD;
for(auto e:G[u]) if(dis[e.v]>dis[u]+e.w) Q.push({dis[e.v]=dis[u]+e.w,e.v});
auto ext=[&](int v) {
if(dis[v]>dis[u]+a[v]) Q.push({dis[v]=dis[u]+a[v],v});
};
if(u<s) ext(to[u]);
else if(d[u]>1) ext(u<<1),ext(u<<1|1);
}
G[s].clear();
for(int x=s>>1;x;x>>=1) if(vis[x]) {
G[s].push_back({x,dis[x]});
int y=to[x]^1;
ans=(ans+f[y]+(dis[x]+a[y])*d[y])%MOD;
}
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=2;i<(1<<n);++i) scanf("%lld",&a[i]);
for(int i=1,u,v,w;i<=m;++i) scanf("%d%d%d",&u,&v,&w),G[v].push_back({u,w});
d[1]=(1<<n)-1,dfs(1);
for(int u=1;u<(1<<n);++u) solve(u);
printf("%lld\n",ans);
return 0;
}
F. [P9482] String
题目大意
给定长度为
的字符串 , 次询问给定 ,求有多少 满足 , 表示字典序比较。 数据范围:
。
思路分析
考虑如何判定一组
那么一组
先考虑怎么对满足第一个条件的点计数,对
然后我们要去掉
第一个条件不好处理,但我们发现
那么我们只要把不满足
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=2e5+5;
mt19937_64 rnd(time(0));
char str[MAXN];
int sa[MAXN],rk[MAXN],wt[MAXN],len[MAXN],ht[MAXN][20];
int bit(int x) { return 1<<x; }
void init(int n) {
iota(sa+1,sa+n+1,1);
sort(sa+1,sa+n+1,[&](int x,int y){ return str[x]<str[y]; });
for(int i=1,j;i<=n;) {
for(j=i;j<n&&str[sa[j+1]]==str[sa[i]];++j);
len[i]=j-i+1;
while(i<=j) rk[sa[i++]]=j;
}
for(int k=1;k<n;k<<=1) {
for(int l=1,r;l<=n;++l) if(len[l]>1) {
r=l+len[l]-1;
for(int i=l;i<=r;++i) wt[sa[i]]=(sa[i]+k>n?0:rk[sa[i]+k]);
sort(sa+l,sa+r+1,[&](int x,int y){ return wt[x]<wt[y]; });
for(int i=l,j;i<=r;) {
for(j=i;j<r&&wt[sa[j+1]]==wt[sa[i]];++j);
len[i]=j-i+1;
while(i<=j) rk[sa[i++]]=j;
}
l=r;
}
}
for(int i=1,k=0;i<=n;++i) {
k=max(k-1,0);
while(str[i+k]==str[sa[rk[i]-1]+k]) ++k;
ht[rk[i]][0]=k;
}
for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
ht[i][k]=min(ht[i][k-1],ht[i+bit(k-1)][k-1]);
}
}
int lcp(int x,int y) {
int l=min(rk[x],rk[y])+1,r=max(rk[x],rk[y]),k=__lg(r-l+1);
return min(ht[l][k],ht[r-bit(k)+1][k]);
}
int n,q,L[MAXN],R[MAXN],id[MAXN],ans[MAXN],d[MAXN],cnt[MAXN];
bool mk[MAXN];
vector <array<int,3>> Q1[MAXN],Q2[MAXN];
struct FenwickTree {
int tr[MAXN],s;
void init() { memset(tr,0,sizeof(tr)); }
void add(int x) { for(;x<=n;x+=x&-x) ++tr[x]; }
int qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
} T[2];
void solve() {
scanf("%d%d%s",&n,&q,str+1);
str[n+1]='#',str[2*n+2]='|';
for(int i=1;i<=n;++i) str[2*n+2-i]=str[i];
init(2*n+2);
for(int i=1;i<=n;++i) R[i]=rk[i],L[i]=rk[2*n+2-i];
for(int i=1;i<n;++i) {
d[i]=lcp(i+1,2*n+2-i);
mk[i]=(R[i+1]<L[i]),cnt[i]=cnt[i-1]+mk[i];
}
iota(id+1,id+n+1,1);
sort(id+1,id+n+1,[&](int x,int y){ return L[x]<L[y]; });
for(int i=1,x,k;i<=q;++i) {
scanf("%d%d",&x,&k),ans[i]=0;
int l=1,r=n,p=n+1;
while(l<=r) {
int m=(l+r)>>1;
if(R[x]<L[id[m]]) p=m,r=m-1;
else l=m+1;
}
if(p<=n) {
Q1[p].push_back({x,k,i});
ans[i]+=cnt[x-1];
Q2[x+k-1].push_back({x,k,i});
}
}
T[0].init(),T[1].init();
for(int i=n;i>=1;--i) {
T[id[i]&1].add(id[i]);
for(auto z:Q1[i]) {
int x=z[0],k=z[1],r=(x^1)&1;
ans[z[2]]+=T[r].qry(x+2*k-1)-T[r].qry(x-1);
}
}
T[0].init();
for(int i=1;i<=n;++i) {
if(mk[i]) T[0].add(i-d[i]+1);
for(auto z:Q2[i]) ans[z[2]]-=T[0].qry(z[0]);
}
for(int i=1;i<=q;++i) printf("%d\n",ans[i]);
for(int i=1;i<=n;++i) Q1[i].clear(),Q2[i].clear();
}
signed main() {
int C,O; scanf("%d%d",&C,&O);
while(O--) solve();
return 0;
}
G. [P9479] Tree
题目大意
给定
个节点的树,保证父亲节点编号小于儿子,求有多少 个节点的树(根节点为 ),使得:
- 对于所有
,在两棵树上 的标号相同。 - 对于所有
,在新树上 。 数据范围:
。
思路分析
考虑
对于
对于原问题,我们依然考虑依次插入
因此对于每个
不难设计出一个 dp,
时间复杂度:
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
int n,m,k,f[1<<10],pc[1<<10];
ll g[1<<10];
void solve() {
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<n;++i) scanf("%*d");
memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
if(!k) {
ll s=1;
for(int i=n;i<n+m;++i) s=s*(2*i-1)%MOD;
return printf("%lld\n",s),void();
}
for(int i=1;i<(1<<k);++i) pc[i]=pc[i>>1]+(i&1);
f[0]=1;
for(int i=n;i<n+m;++i) {
for(int s=0;s<(1<<k);++s) {
if(s&1) g[s>>1]+=f[s];
else {
int z=i+pc[s],t=s>>1;
g[t]+=1ll*f[s]*(2*z-1);
for(int j=0;j<k;++j) if(!(t>>j&1)) g[t|(1<<j)]+=1ll*f[s]*(z-1);
}
}
for(int s=0;s<(1<<k);++s) f[s]=g[s]%MOD,g[s]=0;
}
printf("%d\n",f[0]);
}
signed main() {
int c,T; scanf("%d%d",&c,&T);
while(T--) solve();
return 0;
}
*H. [P9483] Merge
题目大意
给定
个元素,每个元素有 两个属性,初始第 个元素为 ,合并元素 的代价为 ,合并后形成新元素 ,求把所有元素合并成一个的最小代价。 数据范围:
。
思路分析
考虑把元素的合并看成一棵二叉树,记每个点
而
容易发现每个叶子的具体排列不重要,确定二叉树结构后把权值最大的放到系数最小的位置上即可。
因此我们只关心
可以暴力 dp,
转移时枚举两个状态合并,但这样复杂度太高,无法通过。
我们考虑自上而下地维护这棵二叉树:即从根节点开始,每次把树上的一个叶子分裂出两个儿子节点。
但是每次分裂的时候会影响树上原有节点的
考虑优化,注意到一个子树最大深度为
因此存在一种分裂的方式,使得每次分裂后每个非叶节点的最大深度都
但是这么做还不足以通过,首先发现答案不超过
其次我们发现按照上述钦定的过程分裂,前一次分裂过的节点的两个儿子中至少有一个这次操作也会分裂,因此每次分裂的节点数单调不降,记录上一次分裂的节点个数即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1.7e11;
typedef vector<int> vi;
map <vi,array<ll,2>> f[105];
//[tree size][leaf coef] {min val,low}
ll a[105];
void solve() {
int n; scanf("%d",&n);
for(int i=0;i<n;++i) scanf("%lld",&a[i]);
sort(a,a+n,greater<ll>());
ll ans=inf;
for(auto &it:f[n]) {
const vi&c=it.first;
ll s=it.second[0];
for(int i=0;i<n;++i) s+=c[i]*a[i];
ans=min(ans,s);
}
printf("%lld\n",ans);
}
signed main() {
const int n=100;
f[1][{0}]={0,1};
for(int i=1;i<=n;++i) for(auto &it:f[i]) {
const vi&c=it.first;
vi d=it.first;
ll w=it.second[0],lim=it.second[1];
for(int j=1;j<=i&&i+j<=n;++j) {
d.insert(upper_bound(d.begin(),d.end(),c[j-1]+1),c[j-1]+1);
if(j>=lim) {
ll nw=2*w+(i+j-2);
if(nw>inf) break;
if(!f[i+j].count(d)) f[i+j][d]={nw,j};
else {
auto &z=f[i+j][d];
z=min(z,array<ll,2>{nw,j});
}
}
}
}
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
*I. [P8500] Inverse
题目大意
给定序列
,有 个限制,形如 ,求一个满足条件的序列 最小化逆序对数。 数据范围:
。
思路分析
首先发现我们填的所有数一定是某个
先从所有
从前往后确定每个位置,我们只需要最小化未填元素和已填元素之间的逆序对,这个问题和未填元素之间的取值无关,只需要每个未填元素填贪心最优解即可(多解取最小)。
容易发现越靠后的位置填的数一定更大,因为代价函数上填更小的数代价会严格变大。
然后考虑所有
因此注意到区间所有位置填的都
然后就是要解决一个有下界限制的问题,依然考虑贪心,从前往后动态维护每个位置上填数的代价函数,每个位置都贪心取最优解,如果有多个则取最小值。
很显然我们不会选一个比贪心解
如果
因此我们可以依然可以用贪心解决这个问题。
接下来回到原问题,我们要考虑如何确定每个区间中填
倒序扫描
从后往前扫描即可,每个区间如果未满足就把
注意删去被标记为
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef array<int,2> pii;
const int MAXN=1e6+5;
int n,m,q;
struct SegmentTree {
pii tr[MAXN<<2];
int tg[MAXN<<2];
void adt(int p,int k) { tr[p][0]+=k,tg[p]+=k; }
void psd(int p) { adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
void psu(int p) { tr[p]=min(tr[p<<1],tr[p<<1|1]); }
void init(int l=1,int r=q,int p=1) {
tr[p]={0,l},tg[p]=0;
if(l==r) return ;
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
}
void add(int ul,int ur,int k,int l=1,int r=q,int p=1) {
if(ul>ur) return ;
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
psu(p);
}
pii qry(int ul,int ur,int l=1,int r=q,int p=1) {
if(ul<=l&&r<=ur) return tr[p];
int mid=(l+r)>>1; psd(p);
if(ur<=mid) return qry(ul,ur,l,mid,p<<1);
if(mid<ul) return qry(ul,ur,mid+1,r,p<<1|1);
return min(qry(ul,ur,l,mid,p<<1),qry(ul,ur,mid+1,r,p<<1|1));
}
} T;
struct info { int l,r,v; };
vector <info> I[MAXN];
int a[MAXN],dsu[MAXN];
bool up[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void solve() {
scanf("%d%d",&n,&m);
vector <info> lims(m);
vector <int> vals;
for(auto &e:lims) scanf("%d%d%d",&e.l,&e.r,&e.v),vals.push_back(e.v);
sort(vals.begin(),vals.end());
vals.erase(unique(vals.begin(),vals.end()),vals.end());
q=vals.size(),T.init();
for(auto &e:lims){
e.v=lower_bound(vals.begin(),vals.end(),e.v)-vals.begin()+1;
I[e.v].push_back(e);
}
iota(dsu+1,dsu+n+2,1);
for(int i=q;i>=1;--i) {
vector <int> idx;
for(auto o:I[i]) for(int x=find(o.l);x<=o.r;x=find(x)) {
idx.push_back(x),a[x]=i,dsu[x]=x+1;
}
sort(idx.begin(),idx.end()),idx.push_back(n+1);
sort(I[i].begin(),I[i].end(),[&](info x,info y){ return x.l^y.l?x.l>y.l:x.r<y.r; });
int lst=n+1;
vector <info> J;
for(auto o:I[i]) if(o.r<lst) J.push_back(o),lst=o.r;
sort(J.begin(),J.end(),[&](info x,info y){ return x.l>y.l; }),lst=n+1;
for(auto o:J) if(lst>o.r) {
lst=*lower_bound(idx.begin(),idx.end(),o.l);
if(lst<=o.r) up[lst]=true;
else return puts("-1"),void();
}
}
ll ans=0;
for(int i=n;i;--i) if(up[i]) ans+=T.qry(a[i],a[i])[0],T.add(a[i]+1,q,1);
for(int i=1;i<=n;++i) {
if(up[i]) T.add(a[i]+1,q,-1),T.add(1,a[i]-1,1);
else {
auto z=T.qry(max(a[i],1),q,1);
ans+=z[0],T.add(1,z[1]-1,1);
}
}
printf("%lld\n",ans);
}
signed main() {
int cs; scanf("%d",&cs);
while(cs--) {
solve();
for(int i=1;i<=n;++i) a[i]=up[i]=0;
for(int i=1;i<=q;++i) I[i].clear();
}
return 0;
}
*J. [P10881] Cycle
题目大意
给定
个点的完全图,每个点有点权 , 之间的边权为 ,求图上所有生成的基环树的边权乘积之和。 数据范围:
。
思路分析
先从树上边权乘积之和开始,这显然是一个矩阵树定理,我们所求就是
暴力带入得到:
考虑暴力拆开后面的括号,即把原矩阵分成两部分分别求出
又因为
记
此时我们就有了一个较好的表达式来计算矩阵行列式。
然后考虑基环树的情况,我们可以暴力枚举一个环,然后把环缩成一个点后求矩阵树定理的行列式。
先考虑如果确定一个环上的点集
考虑经典做法,拆系数,即把答案表示成
容易发现这个式子中每个
不妨枚举有
我们可以先插入
然后考虑原问题,我们现在要处理的就是
首先矩阵树定理要在原矩阵上去掉一行一列,自然的想法就是把环缩成的虚点对应的行列给删掉。
然后观察
那么我们可以把两部分合起来,综合算出
具体我们依然先枚举环上
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1005,MOD=998244353;
inline void add(ll&x,const ll&y) { x=(x+y)%MOD; }
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n;
ll a[N],A,pwn[N],pwA[N],fac[N],ifac[N],C[N][N],rC[N][N];
ll f[N][N],g[N][N];
//f: coef, g: dp
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]),A=(A+a[i])%MOD;
pwn[0]=pwA[0]=ifac[0]=fac[0]=1;
for(int i=1;i<=n;++i) {
pwn[i]=pwn[i-1]*n%MOD,pwA[i]=pwA[i-1]*A%MOD;
ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
}
for(int i=0;i<=n;++i) {
for(int j=C[i][0]=1;j<=i;++j) {
C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
}
for(int j=0;j<=i;++j) rC[j][i]=C[i][j];
}
for(int i=0;i<=n;++i) for(int j=max(3-2*i,0);2*i+j<=n;++j) {
//cycle with i a^0/a^2 and j a^1
int c=n-2*i-j; ll *bi=rC[i],*bj=rC[j];
ll w=fac[i]*fac[i]%MOD*ifac[2*i]%MOD*fac[2*i+j-1]%MOD;
for(int x=0,y=c;y>=0;++x,--y) { //|S|=0, x a^0, y a^1
add(f[i+x][j+y],w*bi[i+x]%MOD*bj[j+y]%MOD*pwA[x]%MOD*pwn[y]);
}
for(int x=0,y=c-1;y>=0;++x,--y) { //|S|=1, x a^0, y a^1 in Prod
ll z=w*(j+y+1)%MOD*bi[i+x]%MOD*bj[j+y]%MOD;
add(f[i+x][j+y+1],2*(MOD-z)*pwA[x]%MOD*pwn[y]%MOD);
}
for(int x=0,y=c-2;y>=0;++x,--y) { //|S|=2, x a^0, y a^1 in Prod
ll z=w*bi[i+x]%MOD*bj[j+y]%MOD*pwA[x]%MOD*pwn[y]%MOD;
add(f[i+x][j+y+2],z*(j+y+2)*(j+y+1));
add(f[i+x+1][j+y],(MOD-z)*(i+x+1)*(i+1));
}
}
g[0][0]=1;
for(int o=1;o<=n;++o) {
ll w1=a[o],w2=a[o]*a[o]%MOD;
for(int i=o;i>=0;--i) for(int j=o-i;j>=0;--j) {
g[i][j]=(g[i][j]*w2+(i?g[i-1][j]:0)+(j?g[i][j-1]*w1:0))%MOD;
}
}
ll ans=0;
for(int i=0;i<=n;++i) for(int j=0;i+j<=n;++j) ans=(ans+f[i][j]*g[i][j])%MOD;
printf("%lld\n",ans);
return 0;
}
*K. [P9371] Medium
题目大意
给定
,求一个区间 最大化其中位数的出现次数(长度为偶数可以在中间两个元素中任选一个)。 数据范围:
。
思路分析
考虑刻画中位数
那么设
那么一个区间
然后考虑对于每个
把所有
观察构成折线的结构,发现是若干连续的斜率为
那么我们就是要求出距离最远的两条线段,使得在右边的线段上可以选出一个点包含左边线段上至少一个点。
设右侧线段
那么这又变成了一个二维偏序问题,按
由于线段树和当前
预处理每条线段的
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5,inf=1e9,MAXV=2e6+5,V=1e6;
typedef array<int,2> pii;
pii operator +(pii x,pii y) { return {min(x[0],y[0]),max(x[1],y[1])}; }
int n,a[MAXN];
struct SegmentTree {
pii tr[MAXN<<2]; int tg[MAXN<<2];
void adt(int p,int k) { tr[p][0]+=k,tr[p][1]+=k,tg[p]+=k; }
void psd(int p) { if(tg[p]) adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
void psu(int p) { tr[p]=tr[p<<1]+tr[p<<1|1]; }
void add(int ul,int ur,int k,int l=0,int r=n,int p=1) {
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
psu(p);
}
pii qry(int ul,int ur,int l=0,int r=n,int p=1) {
if(ul<=l&&r<=ur) return tr[p];
int mid=(l+r)>>1; psd(p);
if(ur<=mid) return qry(ul,ur,l,mid,p<<1);
if(mid<ul) return qry(ul,ur,mid+1,r,p<<1|1);
return qry(ul,ur,l,mid,p<<1)+qry(ul,ur,mid+1,r,p<<1|1);
}
} X,Y; //X: >=k?1:-1, Y: <=k?1:-1
vector <int> ps[MAXN];
struct FenwickTree {
int tr[MAXV]; vector <int> e;
void build() { fill(tr,tr+MAXV,-inf); }
void upd(int x,int w) { for(e.push_back(x);x<MAXV;x+=x&-x) tr[x]=max(tr[x],w); }
int qry(int x) { int s=-inf; for(;x;x&=x-1) s=max(s,tr[x]); return s; }
void init() { for(int x:e) for(;x<MAXV;x+=x&-x) tr[x]=-inf; e.clear(); }
} T;
int sequence(int N,vector<int>A) {
n=N,T.build();
for(int i=1;i<=n;++i) {
ps[a[i]=A[i-1]].push_back(i);
X.add(i,n,1),Y.add(i,n,-1);
}
int ans=0;
for(int o=1;o<=n;++o) if(ps[o].size()) {
vector <int> p{0};
for(int z:ps[o]) p.push_back(z),Y.add(z,n,2);
p.push_back(n+1);
vector <array<int,3>> Q;
for(int i=1;i<(int)p.size();++i) {
pii x=X.qry(p[i-1],p[i]-1),y=Y.qry(p[i-1],p[i]-1);
Q.push_back({x[0],y[0],-i});
Q.push_back({x[1],y[1],i});
}
sort(Q.begin(),Q.end()),T.init();
for(int i=0,j;i<(int)Q.size();i=j) {
for(j=i;j<(int)Q.size()&&Q[i][0]==Q[j][0];++j) {
if(Q[j][2]<0) T.upd(Q[j][1]+V,Q[j][2]);
else ans=max(ans,Q[j][2]+T.qry(Q[j][1]+V));
}
}
for(int z:ps[o]) X.add(z,n,-2);
}
return ans;
}
*L. [P10302] Distance
题目大意
给定
个点的树和参数 ,把树上距离 的点全连起来, 次询问标号 的点的导出子图中有多少连通块。 数据范围:
。
思路分析
这种复杂连通块计数问题考虑找代表元。
一个很精妙的构造是取每个连通块中 bfs 序最小的点作为代表元,我们有一个优美的性质:一个点是代表元当且仅当其不存在 bfs 序小于其的邻居。
我们发现对于 bfs 序递增的三个节点
根据这个结论,我们知道对于新图上任意一条路径
因此我们能把任意一条路径规约成 bfs 序先降后升的路径,那么如果一个点是代表元,bfs 序小于他的点一定至少对应该点的一条终点 bfs 序小于代表元的出边,这和假设不符。
因此我们证明了一个点
那么我们只要求出每个点距离
距离的限制较为棘手,考虑点分治转成
那么我们按 bfs 序从小到大加,找到
最终答案就变成数
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5,MAXQ=6e5+5,inf=1e9;
int n,m,X,bfn[MAXN],dep[MAXN],siz[MAXN],cur[MAXN],L[MAXN],R[MAXN];
bool vis[MAXN];
vector <int> G[MAXN];
struct SegmentTree {
static const int N=1<<19;
int tr[N<<1];
void init() {
fill(tr,tr+(N<<1),inf);
for(int i=N;i;i>>=1) tr[i]=0;
for(int i=N+n+1;i;i>>=1) tr[i]=0;
}
void upd(int x,int v) { for(x+=N;x;x>>=1) tr[x]=min(tr[x],v); }
void clr(int x) { for(x+=N;tr[x]<inf&&x;x>>=1) tr[x]=inf; }
int qpre(int x,int v) {
for(x+=N;x^1;x>>=1) if((x&1)&&tr[x^1]<=v) {
for(x^=1;x<=N;x=x<<1|(tr[x<<1|1]<=v));
return x-N;
}
return 0;
}
int qsuf(int x,int v) {
for(x+=N;x^1;x>>=1) if((~x&1)&&tr[x^1]<=v) {
for(x^=1;x<=N;x=x<<1|(tr[x<<1]>v));
return x-N;
}
return n+1;
}
} T;
void solve(int u) {
vector <int> a;
function<void(int,int)> dfs2=[&](int x,int fz) {
siz[x]=1;
if(dep[x]<=X) a.push_back(x);
for(int y:G[x]) if(!vis[y]&&y!=fz) {
dep[y]=dep[x]+1,dfs2(y,x),siz[x]+=siz[y];
}
};
dep[u]=0,dfs2(u,0);
sort(a.begin(),a.end(),[&](int x,int y){ return bfn[x]<bfn[y]; });
for(int x:a) {
L[x]=max(L[x],T.qpre(x,X-dep[x]));
R[x]=min(R[x],T.qsuf(x,X-dep[x]));
T.upd(x,dep[x]);
}
for(int x:a) T.clr(x);
}
void dfs1(int u) {
vis[u]=true,solve(u);
for(int v:G[u]) if(!vis[v]) {
int rt=0,tot=siz[v];
function<void(int,int)> dfs3=[&](int x,int fz) {
cur[x]=tot-siz[x];
for(int y:G[x]) if(!vis[y]&&y!=fz) {
dfs3(y,x),cur[x]=max(cur[x],siz[y]);
}
if(!rt||cur[x]<cur[rt]) rt=x;
};
dfs3(v,u),dfs1(rt);
}
}
struct FenwickTree {
int tr[MAXN],s;
void add(int x,int v) { for(;x;x&=x-1) tr[x]+=v; }
int qry(int x) { for(s=0;x<=n;x+=x&-x) s+=tr[x]; return s; }
} F;
vector <array<int,2>> Q[MAXN],M[MAXN];
int ans[MAXQ];
signed main() {
scanf("%d%d%d",&n,&m,&X);
for(int u=2,v;u<=n;++u) {
scanf("%d",&v),G[u].push_back(v),G[v].push_back(u);
}
for(int i=1;i<=n;++i) L[i]=0,R[i]=n+1;
queue <int> q; q.push(1);
for(int o=1;o<=n;++o) {
int u=q.front(); q.pop(),bfn[u]=o;
for(int v:G[u]) if(!bfn[v]) q.push(v);
}
T.init(),dfs1(1);
for(int i=1;i<=n;++i) {
M[i].push_back({L[i],-1}),M[i].push_back({i,1});
M[R[i]].push_back({L[i],1}),M[R[i]].push_back({i,-1});
}
for(int i=1,l,r;i<=m;++i) scanf("%d%d",&l,&r),Q[r].push_back({l,i});
for(int i=1;i<=n;++i) {
for(auto z:M[i]) F.add(z[0],z[1]);
for(auto z:Q[i]) ans[z[1]]=F.qry(z[0]);
}
for(int i=1;i<=m;++i) printf("%d\n",ans[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效