2025 集训记录(7.14~7.22)
CF2004F
并非集训要求做的题,随便开的水题。
*2600,CF 就是喜欢出这种题/yun。
首先注意到区间 \([l,r]\) 的答案最多为 \(r-l\)。
然后注意到区间 \([l,r]\) 内每存在一对前缀和等于后缀和都会使得操作次数减 \(1\)。
即每存在一对 \([i_1,j_1],[i_2,j_2](i_1\le i_2,j_1\le j_2)\) 满足区间和相等,都会使得区间 \([i_1,j_2]\) 的答案减去 \(1\)。于是问题就转换成了求区间和相等的区间对数(由于 \(a_i\ge 1\),显然每对区间都是不存在包含的)。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=2e5+5,INF=2e9,mod=1e9+7;
int t,n,m;
int a[N];
map<int,int> s;
void sol() {
scanf("%d",&n);ll ans=0;
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
ans+=1ll*(i-1)*(n-i+1);
}
s.clear();
for(int i=1;i<=n;++i) {
int sum=0;
for(int j=i;j<=n;++j) {
sum+=a[j],++s[sum];
}
}
for(auto it:s) {
int v=it.se;ans-=1ll*v*(v-1)/2;
}
printf("%lld\n",ans);
}
int main()
{
scanf("%d",&t);
while(t--) {
sol();
}
return 0;
}
洛谷 P5354
大窑讲的全是 Ynoi,吐了。
思路很自然,首先拆位,然后考虑线段树上维护每段区间输入 \(0,1\) 后输出的值。这样直接树剖做是 \(3\) 只 \(\log\)。
优化到 \(2\) 只 \(\log\) 只需要用点位运算将 \(64\) 位的维护全部并到一起即可。
一点都不卡常,作为 Ynoi 还是非常良心了。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<ull,ull>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=2e5+5,INF=2e9,mod=1e9+7;
int t,n,m,k;ull mx=~0;
int a[N];ull v[N];
struct node {
int l,r;ull ls0,ls1,rs0,rs1;
node() {ls0=ls1=rs0=rs1=0;}
}tr[N<<2];
int dfn[N],rnk[N],sz[N],son[N],top[N],fa[N],dep[N],idx=0;
vector<int> e[N];
inline ull get(ull x,pii y) {
return ((~x)&y.fi)|(x&y.se);
}
inline void upd(int u,int x,ull v) {
if(x==1) tr[u].ls0=tr[u].rs0=0,tr[u].ls1=tr[u].rs1=v;
else if(x==2) tr[u].ls0=tr[u].rs0=v,tr[u].ls1=tr[u].rs1=mx;
else if(x==3) tr[u].ls0=tr[u].rs0=v,tr[u].ls1=tr[u].rs1=mx^v;
}
inline void pushup(int u) {
tr[u].ls0=get(tr[u<<1].ls0,{tr[u<<1|1].ls0,tr[u<<1|1].ls1});
tr[u].ls1=get(tr[u<<1].ls1,{tr[u<<1|1].ls0,tr[u<<1|1].ls1});
tr[u].rs0=get(tr[u<<1|1].rs0,{tr[u<<1].rs0,tr[u<<1].rs1});
tr[u].rs1=get(tr[u<<1|1].rs1,{tr[u<<1].rs0,tr[u<<1].rs1});
}
void build(int u,int l,int r) {
tr[u].l=l,tr[u].r=r;
if(l==r) {
return upd(u,a[rnk[l]],v[rnk[l]]);
}
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
}
void update(int u,int ps,int x,ull v) {
if(tr[u].l==tr[u].r) return upd(u,x,v);
int mid=tr[u].l+tr[u].r>>1;
if(ps<=mid) update(u<<1,ps,x,v);
else update(u<<1|1,ps,x,v);
pushup(u);
}
pii query(int u,int l,int r,int op) {
if(l<=tr[u].l&&tr[u].r<=r) {
if(op==0) return {tr[u].ls0,tr[u].ls1};
else return {tr[u].rs0,tr[u].rs1};
}
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid) return query(u<<1,l,r,op);
else if(l>mid) return query(u<<1|1,l,r,op);
else {
pii rs1=query(u<<1,l,r,op),rs2=query(u<<1|1,l,r,op);
if(op==0) return {get(rs1.fi,rs2),get(rs1.se,rs2)};
else return {get(rs2.fi,rs1),get(rs2.se,rs1)};
}
}
void dfs1(int u) {
sz[u]=1,son[u]=0,dep[u]=dep[fa[u]]+1;
for(auto to:e[u]) {
if(to==fa[u]) continue;
fa[to]=u,dfs1(to),sz[u]+=sz[to];
if(!son[u]||sz[to]>sz[son[u]]) {
son[u]=to;
}
}
}
void dfs2(int u,int Top) {
top[u]=Top,dfn[u]=++idx,rnk[idx]=u;
if(son[u]) dfs2(son[u],Top);
for(auto to:e[u]) {
if(to==fa[u]||to==son[u]) continue;
dfs2(to,to);
}
}
ull Query(int u,int v,ull sm) {
vector<pii> s;pii qwq={0,mx},res;
while(top[u]!=top[v]) {
if(dep[top[u]]>dep[top[v]]) {
res=query(1,dfn[top[u]],dfn[u],1);
qwq.fi=get(qwq.fi,res),qwq.se=get(qwq.se,res);
u=fa[top[u]];
}
else {
s.epb(query(1,dfn[top[v]],dfn[v],0));
v=fa[top[v]];
}
}
if(dep[u]<dep[v]) s.epb(query(1,dfn[u],dfn[v],0));
else {
res=query(1,dfn[v],dfn[u],1);
qwq.fi=get(qwq.fi,res),qwq.se=get(qwq.se,res);
}
for(int i=s.size()-1;i>=0;--i) {
qwq.fi=get(qwq.fi,s[i]),qwq.se=get(qwq.se,s[i]);
}
ull nw=0,ans=0;
for(int i=k-1;i>=0;--i) {
if((qwq.fi>>i)&1ull) ans|=(1ull<<i);
else if(((qwq.se>>i)&1ull)&&(nw|(1ull<<i))<=sm) {
nw|=(1ull<<i),ans|=(1ull<<i);
}
}
return ans;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;++i) {
scanf("%d%llu",&a[i],&v[i]);
}
int u,v;
for(int i=1;i<=n-1;++i) {
scanf("%d%d",&u,&v);
e[u].epb(v),e[v].epb(u);
}
fa[1]=0,dfs1(1),dfs2(1,1);
build(1,1,n);
int op,x,y;ull z;
for(int i=1;i<=m;++i) {
scanf("%d%d%d%llu",&op,&x,&y,&z);
if(op==1) printf("%llu\n",Query(x,y,z));
else update(1,dfn[x],y,z);
}
return 0;
}
洛谷 P5314
思维含量不高的卡常题。
经典地,考虑树链剖分后对每个点维护其所有轻儿子的平衡树,每次修改在链加的同时改动链顶(轻儿子)所在平衡树,询问的时候暴力求出自己,父亲,重儿子的值插入平衡树中求第 \(k\) 小即可。
code
拷了一份快读,第一次用平板电视。
#include <bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<ull,ull>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define kth find_by_order
using namespace __gnu_pbds;
using namespace std;
const int N=1e6+5,INF=2e9,mod=1e9+7;
int t,n,m,k;
int a[N],b[N];
int dfn[N],rnk[N],sz[N],son[N],top[N],fa[N],dep[N],idx=0;
tree<pii, null_type, less<pii>, rb_tree_tag,
tree_order_statistics_node_update> S[N];
int tr[N];
char gc() {
constexpr int S = 1 << 23;
static char buf[S], *p1 = buf, *p2 = buf;
return (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, S, stdin), p1 == p2) ? EOF : *p1++);
}
int read() {
int a = 0, b = 0; char c = gc();
while (c < '0' || c > '9') b ^= (c == '-'), c = gc();
while (c >= '0' && c <= '9') a = a * 10 - 48 + c, c = gc();
return b ? -a : a;
}
struct edge {
int ne,to;
}e[N<<1];
int head[N],tot=0;
inline void addedge(int u,int v) {
e[++tot]={head[u],v};head[u]=tot;
}
inline void add(int u,int x) {
for(int i=u;i<=n;i+=i&(-i)) tr[i]+=x;
}
inline int query(int u) {
int res=0;
for(int i=u;i!=0;i-=i&(-i)) res+=tr[i];
return res;
}
void dfs1(int u) {
sz[u]=1,son[u]=0,dep[u]=dep[fa[u]]+1;
for(int i=head[u];i;i=e[i].ne) {
int to=e[i].to;
if(to==fa[u]) continue;
fa[to]=u,dfs1(to),sz[u]+=sz[to];
if(!son[u]||sz[to]>sz[son[u]]) {
son[u]=to;
}
}
}
void dfs2(int u,int Top) {
top[u]=Top,dfn[u]=++idx,rnk[idx]=u;
if(son[u]) dfs2(son[u],Top);
for(int i=head[u];i;i=e[i].ne) {
int to=e[i].to;
if(to==fa[u]||to==son[u]) continue;
S[u].insert({a[to],to}),dfs2(to,to);
}
}
inline void Add(int u,int x) {
int tp=fa[u];
S[tp].erase({a[u]+b[u],u}),b[u]+=x;
S[tp].insert({a[u]+b[u],u});
}
void Update(int u,int v,int w) {
while(top[u]!=top[v]) {
if(dep[top[u]]<dep[top[v]]) swap(u,v);
int to=top[u];
Add(to,w),add(dfn[to],w),add(dfn[u]+1,-w);
u=fa[to];
}
if(dep[u]<dep[v]) {
add(dfn[u],w),add(dfn[v]+1,-w);
if(u==top[u]) Add(u,w);
}
else {
add(dfn[v],w),add(dfn[u]+1,-w);
if(v==top[v]) Add(v,w);
}
}
int main()
{
//freopen("qwq.in","r",stdin);
n=read(),m=read();
for(int i=1;i<=n;++i) a[i]=read();
int op,u,v,w;
for(int i=1;i<=n-1;++i) {
u=read(),v=read();
addedge(u,v),addedge(v,u);
}
dfs1(1),dfs2(1,1);
while(m--) {
op=read();
if(op==1) {
u=read(),v=read(),w=read();
Update(u,v,w);
}
else {
u=read(),w=read();
int x,y,z;
S[u].insert({x=a[u]+query(dfn[u]),u});
if(fa[u]) S[u].insert({y=a[fa[u]]+query(dfn[fa[u]]),fa[u]});
if(son[u]) S[u].insert({z=a[son[u]]+query(dfn[son[u]]),son[u]});
printf("%d\n",S[u].kth(w-1)->fi);
S[u].erase({x,u});
if(fa[u]) S[u].erase({y,fa[u]});
if(son[u]) S[u].erase({z,son[u]});
}
}
return 0;
}
洛谷 P6779
小清新分块。
对一个整块进行讨论:由于每次点只会变浅,且边权大于 \(0\),注意到关键性质,如果一个点踏足了之前已经被块内其它点经过的点,那么它就不是关键的,暂时不用往上跳了。
维护目前所有的“关键”点,每次整块修改只需暴力往上跳,遇到遍历过的点就变为非关键点,每个点只会经过一次,均摊是 \(O(n)\) 的。
但是还有一个问题,单点的往上跳会导致一些非关键点变回关键点。设整块共操作 \(tag\) 次,该点参与了 \(cnt_i\) 次整块操作,那么只需要跳到第 \(tag-cnt_i+1\) 级祖先,如果这个点没被遍历过,那么这个点就可以变回关键点。
查询的话整块就直接输出维护的深度 \(min\),散块的话就直接暴力查询每个散点的第 \(tag-cnt_i\) 级祖先并顺便跳上去即可(这里由于没有多跳所以关键不关键的状态不变)。
又是不卡常的 Ynoi,那很好了。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=2e5+5,B=505,mod=1e9+7;
const ll INF=1e18;
int n,m,rt,sq;
int a[N];
struct edge {
int ne,to,w;
}e[N<<1];
int head[N],tot=0;
inline void add(int u,int v,int w) {
e[++tot]={head[u],v,w},head[u]=tot;
}
struct que {
int op,l,r;
}que[N];
int sz[N],dfn[N],rnk[N],low[N],dep[N],fa[N],son[N],top[N],idx=0;
int bel[N],st[B],ed[B];ll val[N],ans[N];
int vis[N],q[N],cnt[N],in[N],len=0;
void dfs(int u) {
dep[u]=dep[fa[u]]+1,sz[u]=1;
for(int i=head[u];i;i=e[i].ne) {
int to=e[i].to;
if(to==fa[u]) continue;
val[to]=val[u]+e[i].w;
fa[to]=u,dfs(to),sz[u]+=sz[to];
if(!son[u]||sz[to]>sz[son[u]]) son[u]=to;
}
}
void dfs2(int u,int Top) {
top[u]=Top,dfn[u]=++idx,rnk[idx]=u;
if(son[u]) dfs2(son[u],Top);
for(int i=head[u];i;i=e[i].ne) {
int to=e[i].to;
if(to==fa[u]||to==son[u]) continue;
dfs2(to,to);
}
}
inline int kth(int u,int k) {
if(dep[u]<=k) return rt;
while(k>dfn[u]-dfn[top[u]]) {
k-=dfn[u]-dfn[top[u]]+1,u=fa[top[u]];
}
return rnk[dfn[u]-k];
}
void sol(int id) {
int L=st[id],R=ed[id];
int tag=0;ll mi=INF;len=0;
for(int i=1;i<=n;++i) vis[i]=0;
for(int i=L;i<=R;++i) {
q[i]=cnt[i]=in[i]=0;
if(!vis[a[i]]) {
vis[a[i]]=1,q[++len]=i,in[i]=1;
mi=min(mi,val[a[i]]);
}
}
for(int i=1;i<=m;++i) {
int l=max(que[i].l,L),r=min(que[i].r,R);
if(l>r) continue;
if(que[i].op==1) {
if(l==L&&r==R) {
++tag;int tmp=len;len=0;
for(int j=1;j<=tmp;++j) {
int u=q[j];a[u]=fa[a[u]];
++cnt[u],mi=min(mi,val[a[u]]);
if(!vis[a[u]]) {
vis[a[u]]=1,q[++len]=u;
}
else in[u]=0;
}
}
else {
for(int j=l;j<=r;++j) {
a[j]=kth(a[j],tag-cnt[j]+1);
mi=min(mi,val[a[j]]),cnt[j]=tag;
if(!in[j]) {
vis[a[j]]=1,q[++len]=j,in[j]=1;
}
}
}
}
else {
if(l==L&&r==R) ans[i]=min(ans[i],mi);
else {
for(int j=l;j<=r;++j) {
a[j]=kth(a[j],tag-cnt[j]),cnt[j]=tag;
ans[i]=min(ans[i],val[a[j]]);
}
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&rt);
int u,v,w;
for(int i=1;i<=n-1;++i) {
scanf("%d%d%d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
dfs(rt),dfs2(rt,rt);
sq=550;int all=(n-1)/sq+1;
for(int i=1;i<=all;++i) {
st[i]=(i-1)*sq+1;
ed[i]=(i==all)?n:i*sq;
for(int j=st[i];j<=ed[i];++j) {
bel[j]=i;
}
}
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
}
for(int i=1;i<=m;++i) {
scanf("%d%d%d",&que[i].op,&que[i].l,&que[i].r);
ans[i]=INF;
}
for(int i=1;i<=all;++i) sol(i);
for(int i=1;i<=m;++i) {
if(que[i].op==2) printf("%lld\n",ans[i]);
}
return 0;
}
洛谷 P5305
小朋友时代的时候,不懂得看数据范围,扔了一个暴力上去以为自己无敌了,结果喜提 0 pts。
现在做这题,一发通过!
首先你要会 P4211。这是典题。
然后你注意到这题仅仅只是将求区间和变成了求区间带权和。具体地,设 \(dfn\) 序第 \(i\) 项对应的点为 \(rnk_i\),该点带的权就是 \(dep_{rnk_i}^k-dep_{rnk_i}^{k-1}\)。我们要求线段树上每个点的值乘上其对应权的区间和。
code
//writer:Oier_szc
#include <bits/stdc++.h>
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define ll long long
using namespace std;
const int N=5e4+5,mod=998244353;
int n,m,k;
struct Question {
int u,z,q_id;
bool operator<(const Question &W) const {
return u<W.u;
}
}q[N<<1];
struct edge {
int ne,to,w;
}e[N<<1];
int head[N],tot=0;
ll qpow(ll x,int y) {
ll res=1;
while(y) {
if(y&1) res=res*x%mod;
x=x*x%mod,y>>=1;
}
return res;
}
void add(int u,int v) {
e[++tot]={head[u],v},head[u]=tot;
}
struct que {
int op,l,r;
}que[N];
int sz[N],dfn[N],rnk[N],low[N],dep[N],fa[N],son[N],top[N],idx=0;
ll ans[N];
void dfs(int u) {
dep[u]=dep[fa[u]]+1,sz[u]=1;
for(int i=head[u];i;i=e[i].ne) {
int to=e[i].to;
if(to==fa[u]) continue;
fa[to]=u,dfs(to),sz[u]+=sz[to];
if(!son[u]||sz[to]>sz[son[u]]) son[u]=to;
}
}
void dfs2(int u,int Top) {
top[u]=Top,dfn[u]=++idx,rnk[idx]=u;
if(son[u]) dfs2(son[u],Top);
for(int i=head[u];i;i=e[i].ne) {
int to=e[i].to;
if(to==fa[u]||to==son[u]) continue;
dfs2(to,to);
}
}
struct tree {
int l,r,tag;ll sum,_sum;
}tr[N<<2];
inline void pushup(int u) {
tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%mod;
}
inline void Add(int u,int v) {
tr[u].sum=(tr[u].sum+tr[u]._sum*v)%mod;
tr[u].tag+=v;
}
inline void pushdown(int u) {
if(!tr[u].tag) return;
Add(u<<1,tr[u].tag),Add(u<<1|1,tr[u].tag);
tr[u].tag=0;
}
void build(int u,int l,int r) {
tr[u].l=l,tr[u].r=r;
if(l==r) {
int p=rnk[l];
tr[u]._sum=(qpow(dep[p],k)-qpow(dep[p]-1,k)+mod)%mod;
return;
}
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
tr[u]._sum=(tr[u<<1]._sum+tr[u<<1|1]._sum)%mod;
}
void update(int u,int l,int r,int x) {
if(l<=tr[u].l&&tr[u].r<=r) {
return Add(u,x);
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) update(u<<1,l,r,x);
if(r>mid) update(u<<1|1,l,r,x);
pushup(u);
}
ll query(int u,int l,int r) {
if(l<=tr[u].l&&tr[u].r<=r) {
return tr[u].sum;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;ll res=0;
if(l<=mid) res=(res+query(u<<1,l,r))%mod;
if(r>mid) res=(res+query(u<<1|1,l,r))%mod;
return res;
}
inline void updatepath(int u,int x) {
while(u) {
update(1,dfn[top[u]],dfn[u],x);
u=fa[top[u]];
}
}
inline ll querypath(int u) {
ll res=0;
while(u) {
res=(res+query(1,dfn[top[u]],dfn[u]))%mod;
u=fa[top[u]];
}
return res;
}
signed main()
{
scanf("%d%d%d",&n,&m,&k);
int Fa;
for(int i=2;i<=n;++i) {
scanf("%d",&Fa);
add(Fa,i);
}
int x,y;
for(int i=1;i<=m;++i) {
scanf("%d%d",&x,&y);
q[i].q_id=i,q[i].u=x,q[i].z=y;
}
sort(q+1,q+1+m);
dfs(1),dfs2(1,1),build(1,1,n);
int now=1;
while(q[now].u==0) ++now;
for(int i=1;i<=n;++i) {
updatepath(i,1);
while(now<=m&&q[now].u==i) {
ans[q[now].q_id]=querypath(q[now].z);
++now;
}
if(now>m) break;
}
for(int i=1;i<=m;++i) {
printf("%lld\n",ans[i]);
}
return 0;
}
7.15 模拟赛 T1
又是没做出人均题的一天,嘻嘻。
题意
给你一个点数为 \(n\) 的 DAG,边权为 \(1\),现有 \(q\) 次询问,询问有三种:\(op=1\) 则求出不经过点 \(u\) 的最长路径。\(op=2\) 则求出必须经过点 \(u\) 的最长路径。\(op=3\) 则必须经过边 \(id\) 的最长路径。
sol1(分治)
显然后两种操作是一眼的,这题的难点其实只有第一种操作。
首先你得做过 CF1442D。
然后观察到这一题也是对于拓扑序上每个点求出不算这个点的最大值,而且一个点对其它点的贡献也能快速算出来,做法是能套用的。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=2e5+5,INF=2e9,mod=1e9+7;
int n,m,q;
vector<int> e1[N],e2[N];
int u[N],v[N],a[N],b[N],in[N],out[N],len=0;
int ff[N],gg[N],f[N],g[N],ans[N],mx=0;
struct ret {
int op,u,v;
};
void solve(int l,int r) {
if(l==r) {
ans[a[l]]=mx;return;
}
int mid=l+r>>1;
vector<ret> ps;int _mx=mx;
for(int i=l;i<=mid;++i) {
int u=a[i];
for(auto to:e1[u]) {
ps.pb({0,to,f[to]});
f[to]=max(f[to],f[u]+1);
if(b[to]<=mid||b[to]>r) mx=max(mx,f[to]+g[to]);
}
for(auto to:e2[u]) {
ps.pb({1,to,g[to]});
g[to]=max(g[to],g[u]+1);
if(b[to]<=mid||b[to]>r) mx=max(mx,f[to]+g[to]);
}
}
solve(mid+1,r);
for(int i=ps.size()-1;i>=0;--i) {
if(!ps[i].op) f[ps[i].u]=ps[i].v;
else g[ps[i].u]=ps[i].v;
}
ps.clear(),mx=_mx;
for(int i=r;i>=mid+1;--i) {
int u=a[i];
for(auto to:e1[u]) {
ps.pb({0,to,f[to]});
f[to]=max(f[to],f[u]+1);
if(b[to]<l||b[to]>mid) mx=max(mx,f[to]+g[to]);
}
for(auto to:e2[u]) {
ps.pb({1,to,g[to]});
g[to]=max(g[to],g[u]+1);
if(b[to]<l||b[to]>mid) mx=max(mx,f[to]+g[to]);
}
}
solve(l,mid);
for(int i=ps.size()-1;i>=0;--i) {
if(!ps[i].op) f[ps[i].u]=ps[i].v;
else g[ps[i].u]=ps[i].v;
}
ps.clear(),mx=_mx;
}
int main()
{
freopen("storm_wind.in","r",stdin);
freopen("storm_wind.out","w",stdout);
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;++i) {
scanf("%d%d",&u[i],&v[i]);
e1[u[i]].epb(v[i]),e2[v[i]].epb(u[i]);
++in[v[i]],++out[u[i]];
}
queue<int> que;
for(int i=1;i<=n;++i) {
if(!in[i]) que.push(i);
}
while(!que.empty()) {
int u=que.front();que.pop();
a[++len]=u,b[u]=len;
for(auto to:e1[u]) {
ff[to]=max(ff[to],ff[u]+1),--in[to];
if(!in[to]) que.push(to);
}
}
for(int i=1;i<=n;++i) {
if(!out[i]) que.push(i);
}
while(!que.empty()) {
int u=que.front();que.pop();
for(auto to:e2[u]) {
gg[to]=max(gg[to],gg[u]+1),--out[to];
if(!out[to]) que.push(to);
}
}
solve(1,n);int op,x;
while(q--) {
scanf("%d%d",&op,&x);
if(op==1) {
printf("%d\n",ans[x]);
}
else if(op==2) {
printf("%d\n",ff[x]+gg[x]);
}
else {
printf("%d\n",ff[u[x]]+gg[v[x]]+1);
}
}
return 0;
}
sol2(官解/大众解)
这个做法类似于 CF1163F,能算近似原。
在 CF1163F 中,我们拉出来了一条最短路,然后考虑了 经过每个边的最短路 和 基准最短路 中间的不重合段是 一段区间 ,最后直接线段树即可解决问题。
这题也是类似的,只是在拓扑序上搞。
为啥这题能过这么多人?虽然近似原是水 *3000 但是大家都强到人均切 了???妈的近似原都做过这题为啥还能搞砸。
代码咕咕咕。
7.15 模拟赛 T2
题意
单点修改区间 \(mex\),强制在线。\(n,q\le 10^5\)。
sol
出乎意料的简单。
直接大力 bitset+分块,块长取 \(2000\),维护 bitset \(s_{i,j}\) 表示 \(i\) 到 \(j\) 块出现的数,修改直接暴力改 \(s_{i,j}\),查询整块部分直接读取,散块暴力插入 bitset。
复杂度能过,很快。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define print(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e5+5,B=2000,M=N/B+5,INF=2e9,mod=1e9+7;
int t,n,q,c;
int a[N];
bitset<N> b[M][M],tmp;
int bel[N],st[M],ed[M];
int main()
{
freopen("mex.in","r",stdin);
freopen("mex.out","w",stdout);
scanf("%d",&t);
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
}
c=(n-1)/B+1;
for(int i=1;i<=c;++i) {
st[i]=(i-1)*B+1;
ed[i]=(i==c)?n:i*B;
for(int j=st[i];j<=ed[i];++j) {
bel[j]=i,b[i][i][a[j]]=1;
}
}
for(int i=1;i<=c;++i) {
for(int j=i+1;j<=c;++j) {
b[i][j]=b[i][j-1]|b[j][j];
}
}
int op,x,y,ls=0;
for(int i=1;i<=q;++i) {
scanf("%d%d%d",&op,&x,&y);
x=(x+t*ls)%n+1,y=(y+t*ls)%n+op;
if(op==0) {
int id=bel[x],pre=a[x],cnt=0;
for(int j=st[id];j<=ed[id];++j) {
cnt+=(a[j]==pre);
}
for(int j=1;j<=id;++j) {
for(int k=id;k<=c;++k) {
if(!b[j][id-1][pre]&&!b[id+1][k][pre]&&cnt==1) {
b[j][k][pre]=0;
}
b[j][k][y]=1;
}
}
a[x]=y;
}
else {
if(x>y) swap(x,y);
int dx=bel[x],dy=bel[y];tmp.reset();
if(dx==dy) {
for(int j=x;j<=y;++j) {
tmp[a[j]]=1;
}
}
else {
tmp=b[dx+1][dy-1];
for(int j=x;j<=ed[dx];++j) {
tmp[a[j]]=1;
}
for(int j=st[dy];j<=y;++j) {
tmp[a[j]]=1;
}
}
tmp.flip();
printf("%d\n",ls=tmp._Find_first());
}
}
return 0;
}
7.15 模拟赛 T3
题意
有一个森林,森林中的森林中每个树的根节点上有棋子若干。
现在给森林新增 \(Q\) 次树,每次先给出 \(op\) 表示操作类型和 \(c_i\) 表示根节点的棋子数量,如果 \(op=1\),给定 \(x\) 表示新增的树是第 \(x\) 次加入的树的拷贝,如果 \(op=2\),给出一棵全新的树。
现在有两个人玩游戏,每次选择森林中的任意一个棋子移动到其所在点的任意一个儿子。在玩游戏之前森林会进行一轮异变,每一个叶子有 \(\frac{1}{2}\) 的概率不变,有 \(\frac{1}{2}\) 的概率在其下新长出一个叶子。
在每次给森林新增树后,请回答先手有必胜策略的概率。
\(Q\le 10^5\),所有加入的树的点数之和 \(S\le 10^6\)。 每个根的棋子个数显然不重要。
sol
设每个点 \(sg\) 能取到的最大值设为 \(v_i\),即下文每个点 DP 的上界。
首先一个显然的想法是大力 \(DP\),设 \(f_{u,i}\) 表示节点 \(u\) 的 \(SG\) 值是 \(i\) 的方案数,转移考虑状压 DP。我们有结论 1,大小为 \(n\) 的子树,\(v_u\) 是 \(O(\log n)\) 的级别。容易得到这一部分总复杂度 \(O(n^2)\)。
新增树的话就直接暴力跑异或卷积(这一部分是 \(O(q \log^2 n)\) 的)。
只要一步简单的优化即可将 \(DP\) 过程的复杂度也做到双 \(\log\)。具体地,每个点根据 \(v_i\) 从小到大排序,然后依次转移,当前的 DP 上界为目前扫到的 \(2^{v_i}\) 最大值。容易得到这样做的复杂度变为 \(\sum_{i=1}^{n}v_i2^{v_i}\)。
我们有结论 2,\(v_i=k\) 的点个数是 \(\frac{n}{2^k}\) 的级别。于是上式为 \(O(n \log^2 n)\)。
总复杂度 \(O((n+q) \log^2 n)\),空间复杂度 \(O((n+q) \log n)\)。
code
有点小细节,但还是很好写的。
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e6+5,INF=2e9,mod=1004535809;
int t,n,m;
int sg[N];
ll f[N][35],g[N*5],_g[N*5],ans[35],pre[35];
int rt[N],fa[N],c[N],sz[N],idx=0;
vector<int> e[N];
int find(int u) {
if(u==fa[u]) return u;
else return fa[u]=find(fa[u]);
}
void dfs(int u) {
vector<int> s;
for(auto to:e[u]) {
dfs(to),s.epb(to);
}
if(!s.size()) {
sg[u]=1,f[u][0]=f[u][1]=1;
return;
}
sort(all(s),[&](int x,int y) {
return sg[x]<sg[y];
});
int mx=0,qwq=sg[s.back()]+1;
for(int i=0;i<(1<<qwq);++i) g[i]=0;
g[0]=1;
for(auto it:s) {
mx=max(mx,sg[it]+1);
for(int i=0;i<(1<<mx);++i) {
_g[i]=g[i],g[i]=0;
}
for(int i=0;i<=sg[it];++i) {
for(int j=0;j<(1<<mx);++j) {
g[j|(1<<i)]=(g[j|(1<<i)]+_g[j]*f[it][i])%mod;
}
}
}
for(int i=0;i<(1<<mx);++i) {
if(!g[i]) continue;
int ps=__builtin_ctz(~i);
sg[u]=max(sg[u],ps);
f[u][ps]=(f[u][ps]+g[i])%mod;
}
}
void merge(int u,int op) {
u=rt[u];
for(int i=0;i<32;++i) {
pre[i]=ans[i],ans[i]=0;
}
for(int i=0;i<32;++i) {
if(!pre[i]) continue;
for(int j=0;j<32;++j) {
ans[i^(j*op)]=(ans[i^(j*op)]+pre[i]*f[u][j])%mod;
}
}
}
int main()
{
scanf("%d",&m);int op,x;ans[0]=1;
for(int i=1;i<=m;++i) {
scanf("%d%d",&c[i],&op);
if(op==1) {
scanf("%d",&x);fa[i]=x;
merge(find(x),c[i]&1);
}
else {
fa[i]=i,rt[i]=idx+1;
scanf("%d",&sz[i]);
for(int j=2;j<=sz[i];++j) {
int u;scanf("%d",&u);
e[u+idx].epb(j+idx);
}
dfs(idx+1),idx+=sz[i];
merge(i,c[i]&1);
}
ll sum=0;
for(int i=1;i<32;++i) {
sum=(sum+ans[i])%mod;
}
printf("%lld\n",sum);
}
return 0;
}
7.17 模拟赛 T1
题意
给定 \(n\times n\) 的网格,初始所有格子没有激活,\(q\) 次修改每次翻转一个格子的状态(激活/不激活)。对于每次修改求出网格中 激活 的 恰好为一个矩形 的 极大 连通块数量。
\(n,q\le 5\times 10^5\)。
sol
注意到一个连通块是矩形当且仅当 横直径 \(\times\) 竖直径 \(=\) 连通块大小。
然后直接上线段树分治维护这个东西即可。
code
在模拟赛还剩 \(40\) min 的时候注意到写假了,在最后 \(6\) min 的时候 写+调 极限过大样例,最后直接过了。
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=6e5+5,INF=2e9,mod=1e9+7;
int t,n,q;
int x[N],y[N],xx[N],yy[N];map<pii,int> id;
vector<int> e[N];
#define get(x,y) ((x-1)*n+y);
struct node {
int l,r;vector<int> S;
}tr[N<<2];
int dr[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
struct back1 {
int u,v,fa,sz;
int c[5];
};
struct back2 {
int x,y,z,id;
};
inline int in(int x,int y) {
return x>=1&&x<=n&&y>=1&&y<=n;
}
int fa[N],sz[N],cz[N],idx=0;
int c[N][4];//0 xmx 1 xmi 2 ymx 3 ymi
vector<int> app[N];
back1 stk1[N];int top1=0;
int allcnt=0,okcnt=0;pii ans[N];
int find(int u) {
if(u==fa[u]) return u;
else return find(fa[u]);
}
int check(int u) {
return sz[u]==(1ll*(c[u][0]-c[u][1]+1)*(c[u][2]-c[u][3]+1));
}
void merge(int u,int v) {
u=find(u),v=find(v);
if(u!=v) {
if(sz[u]>sz[v]) swap(u,v);
--allcnt,++top1;
okcnt-=check(u),okcnt-=check(v);
auto &it=stk1[top1];
it.u=u,it.v=v,it.fa=fa[u],it.sz=sz[v];
fa[u]=v,sz[v]+=sz[u];
it.c[0]=c[v][0],c[v][0]=max(c[v][0],c[u][0]);
it.c[2]=c[v][2],c[v][2]=max(c[v][2],c[u][2]);
it.c[1]=c[v][1],c[v][1]=min(c[v][1],c[u][1]);
it.c[3]=c[v][3],c[v][3]=min(c[v][3],c[u][3]);
okcnt+=check(v);
}
}
inline void roll1() {
auto &it=stk1[top1];
fa[it.u]=it.fa,sz[it.v]=it.sz;
for(int j=0;j<4;++j) {
c[it.v][j]=it.c[j];
}
--top1;
}
void add(int u) {
++allcnt,++okcnt,cz[u]=1;
for(auto to:e[u]) {
if(cz[to]) merge(u,to);
}
}
void build(int u,int l,int r) {
tr[u].l=l,tr[u].r=r;
if(l==r) return;
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
void update(int u,int l,int r,int x) {
if(l<=tr[u].l&&tr[u].r<=r) {
tr[u].S.epb(x);return;
}
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) update(u<<1,l,r,x);
if(r>mid) update(u<<1|1,l,r,x);
}
void query(int u) {
int _allcnt=allcnt,_okcnt=okcnt,_top1=top1;
for(auto it:tr[u].S) {
add(it);
}
if(tr[u].l==tr[u].r) {
ans[tr[u].l]={allcnt,okcnt};
}
else query(u<<1),query(u<<1|1);
allcnt=_allcnt,okcnt=_okcnt;
while(top1>_top1) roll1();
for(auto it:tr[u].S) cz[it]=0;
}
int main()
{
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
scanf("%d%d",&n,&q);
for(int i=1;i<=q;++i) {
scanf("%d%d",&x[i],&y[i]);int ID;
if(!id[{x[i],y[i]}]) {
id[{x[i],y[i]}]=ID=++idx;
xx[idx]=x[i],yy[idx]=y[i];
}
else ID=id[{x[i],y[i]}];
app[ID].epb(i);
}
for(auto it:id) {
pii u=it.fi;int ID=it.se;
for(int j=0;j<4;++j) {
int tx=u.fi+dr[j][0];
int ty=u.se+dr[j][1];
if(in(tx,ty)&&id.count({tx,ty})) {
e[ID].epb(id[{tx,ty}]);
}
}
}
build(1,1,q);
for(int i=1;i<=idx;++i) {
int Sz=app[i].size();
fa[i]=i,sz[i]=1;
c[i][0]=c[i][1]=xx[i];c[i][2]=c[i][3]=yy[i];
if(Sz&1) ++Sz,app[i].epb(q+1);
for(int j=0;j<Sz;j+=2) {
update(1,app[i][j],app[i][j+1]-1,i);
}
}
query(1);
for(int i=1;i<=q;++i) {
printf("%d ",ans[i].se);
if(ans[i].fi==ans[i].se) printf("1\n");
else printf("0\n");
}
return 0;
}
7.17 模拟赛 T2
今天真的还没遇到什么有意思的题,拿这个板子凑数罢。
题意
有 \(n\) 个物品,每个物品有价值 \(w_i\),现在选择恰好 \(k\) 个物品,连续的 \(m\) 个物品最多选一个,求价值总和的最大值。
\(m,k\le n\le 2\times 10^6\)。
sol
注意到多取一个物品带来的最大收益是递减的。也就是函数具有凸性。
同时又有恰好 \(k\) 个物品的限制,典型的 wqs 二分。
复杂度 \(O(n\log V)\)。
code
需要 __int128,那很恶心了。
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<__int128,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=2e6+5,mod=1e9+7;
const ll INF=1e15;
int t,n,m,k;
int a[N];__int128 f[N],pf[N];int g[N],pg[N];
pii check(ll v) {
pii ans={0,0};
for(int i=1;i<=n;++i) {
if(i<m) f[i]=a[i]-v,g[i]=1;
else f[i]=pf[i-m]+a[i]-v,g[i]=pg[i-m]+1;
if(f[i]>pf[i-1]) {
pf[i]=f[i],pg[i]=g[i];
}
else pf[i]=pf[i-1],pg[i]=pg[i-1];
if(f[i]>ans.fi) ans={f[i],g[i]};
}
return ans;
}
int main()
{
freopen("reward.in","r",stdin);
freopen("reward.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);
}
ll l=-INF,r=INF;ll ans=0,val=0;
while(l<=r) {
ll mid=l+r>>1;pii res=check(mid);
if(res.se>k) {
l=mid+1;
}
else {
ans=res.fi;
r=mid-1,val=mid;
}
}
printf("%lld\n",ans+val*k);
return 0;
}
牛客多校 Day 2 B
感觉是 5 个签子里面质量还行的一道。难度约等于 Div2 B。也是凑数的。
题意
给定序列 \(a\),定义序列的权值为 \(\sum_{i=1}^{n}2^{a_i}\)。现在可以进行一次操作,将 \(a_i\) 和 \(a_j\)(\(i\ne j\))并删去,再加入 \(a_i \oplus a_j\)。
判断:该序列是否无论如何操作权值都不会变小。
sol
首先由于是 \(2^{a_i}\),可以注意到问题其实就是判断是否对于任意的 \(i,j\) 都有 \(a_i \oplus a_j>\max(a_i,a_j)\)。这个条件又可以转换成两个数中至少一个数的最高位在异或的时候被消了。
把最高位拉出来,如果但凡对于某一个数有一个其它数含有其最高位,那就不合法。要是没有这种情况就合法。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=5e5+5,INF=2e9,mod=1e9+7;
int t,n,m;
ll a[N];int cnt[66],cnt2[66];
void sol() {
scanf("%d",&n);
for(int i=0;i<=62;++i) cnt[i]=cnt2[i]=0;
for(int i=1;i<=n;++i) {
scanf("%lld",&a[i]);
int mx=0;
for(int j=0;j<=62;++j) {
if((a[i]>>j)&1ll) {
++cnt2[j],mx=j;
}
}
++cnt[mx];
}
for(int i=0;i<=62;++i) {
if(cnt[i]&&cnt2[i]>1) {
puts("NO");return;
}
}
puts("YES");
}
int main()
{
scanf("%d",&t);
while(t--) {
sol();
}
return 0;
}
CF436E
先看 这位 的题解。讲的抽象代码抽象看不懂一点。再看 这位 的题解,感觉好理解好写,写写写,边界判到吐,好在过了。
不需要反悔贪心。考虑两个星两个星选,每次有两个选择,选两个一星或者选一个两星,需要权衡哪个最大。
直接维护两个堆考虑这两种情况即可。注意如果某个关卡已经选了一星就不能再选两星了,从第二个堆里面踢出。
然后就是一堆边界处理,非常的史。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=3e5+5,INF=2e9,mod=1e9+7;
#define que priority_queue<pii,vector<pii>,greater<pii> >
#define id(x) (x>n?(x-n):x)
int t,n,m;
int a[N<<1];
que q,q2;
int cnt[N<<1],vis[N<<1];
void kick(que &q) {
while(!q.empty()&&vis[q.top().se]) q.pop();
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
scanf("%d%d",&a[i],&a[i+n]);a[i+n]-=a[i];
q.push({a[i],i}),q2.push({a[i]+a[i+n],i});
}
a[n+n+1]=INF;
ll ans=0;
if(m&1) q.push({0,0}),++m;
while(m) {
kick(q),kick(q2);
int w1=q.empty()?(n+n+1):q.top().se;
if(!q.empty()) q.pop(),kick(q);
int w2=q.empty()?(n+n+1):q.top().se;
int w3=q2.empty()?(n+n+1):q2.top().se;
int x=(w2==n+n+1)?INF:(a[w1]+a[w2]);
int y=(w3==n+n+1)?INF:(a[w3]+a[w3+n]);
if(x<y) {
ans+=x;if(!q.empty()) q.pop();
++cnt[id(w1)],++cnt[id(w2)],vis[w1]=vis[w2]=1;
if(w1&&w1<=n) q.push({a[w1+n],w1+n});
if(w2&&w2<=n) q.push({a[w2+n],w2+n});
}
else {
ans+=y;if(!q2.empty()) q2.pop();
cnt[w3]+=2,vis[w3]=vis[w3+n]=1;
q.push({a[w1],w1});
}
m-=2;
}
printf("%lld\n",ans);
for(int i=1;i<=n;++i) {
printf("%d",cnt[i]);
}
puts("");
return 0;
}
AGC066A
题做过,复制一下之前写的题解。
构造这么好玩。笑点解析:不必代价最小化。
先将网格二分图染色。两个可能的构造方案是:
白格子取最靠近的 \(x\) 使得 \(\frac{x}{d}\) 为奇数,
黑格子取最靠近的 \(x\) 使得 \(\frac{x}{d}\) 为偶数。
白格子取最靠近的 \(x\) 使得 \(\frac{x}{d}\) 为偶数,
黑格子取最靠近的 \(x\) 使得 \(\frac{x}{d}\) 为奇数数。
显然一定有一种的代价不超过 \(\frac{dn^2}{2}\)。
有点小细节。
code
//writer:Oier_szc
#include <bits/stdc++.h>
//#include <windows.h>
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pii pair<int,int>
#define fi first
#define se second
//#define int long long
//#define uint unsigned long long
using namespace std;
const int N=505,INF=2e9,mod=1e9+7;
int n,d;
int a[N][N],b[N][N],c[N][N];
void print(int s[N][N]) {
for(int i=1;i<=n;++i) {
for(int j=1;j<=n;++j) {
printf("%d ",s[i][j]);
}
printf("\n");
}
}
int main()
{
scanf("%d%d",&n,&d);
for(int i=1;i<=n;++i) {
for(int j=1;j<=n;++j) {
scanf("%d",&a[i][j]);
}
}
//cr(lower(-6))
int ans1=0,ans2=0;
for(int i=1;i<=n;++i) {
for(int j=1;j<=n;++j) {
int lw=a[i][j]/(2*d)*(2*d),up;
if(lw<a[i][j]) {
if(lw+2*d-a[i][j]<a[i][j]-lw) lw+=2*d;
}
else {
if(a[i][j]-lw+2*d<lw-a[i][j]) lw-=2*d;
}
if(lw<a[i][j]) up=lw+d;
else up=lw-d;
if((i+j)&1) {
if((lw/d)&1) {
ans1+=abs(a[i][j]-lw),ans2+=abs(up-a[i][j]);
b[i][j]=lw,c[i][j]=up;
}
else {
ans1+=abs(up-a[i][j]),ans2+=abs(a[i][j]-lw);
b[i][j]=up,c[i][j]=lw;
}
}
else {
if(!((lw/d)&1)) {
ans1+=abs(a[i][j]-lw),ans2+=abs(up-a[i][j]);
b[i][j]=lw,c[i][j]=up;
}
else {
ans1+=abs(up-a[i][j]),ans2+=abs(a[i][j]-lw);
b[i][j]=up,c[i][j]=lw;
}
}
}
}
if(ans1<ans2) print(b);
else print(c);
return 0;
}
ARC170C
首先如果 \(m\ge n\) 的话显然无论如何 \(\mathrm{mex}\) 都不会超过 \(m\),所以 \(s_i=1\) 就是 \(1\) 种,否则 \(m\) 种。
如果 \(m<n\) 就将问题变成方格染色,\(s_i=1\) 就强制染最左边没填的,否则随便染(可重复染)但不能填最左边没填的。设计 \(dp_{i,j}\) 表示考虑了 \(i\) 个数,方格中已经染了 \(j\) 个颜色的方案数。转移平凡。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=5e3+5,INF=2e9,mod=998244353;
int t,n,m;
int s[N];
ll f[N][N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) {
scanf("%d",&s[i]);
}
if(m>=n) {
ll ans=1;
for(int i=1;i<=n;++i) {
if(!s[i]) ans=ans*m%mod;
}
printf("%lld\n",ans);
}
else {
f[0][0]=1;
for(int i=1;i<=n;++i) {
if(s[i]) {
for(int j=1;j<=m+1;++j) {
f[i][j]=f[i-1][j-1];
}
}
else {
for(int j=1;j<=m+1;++j) {
f[i][j]=(f[i-1][j]*j+f[i-1][j-1]*(m-j+1))%mod;
}
}
}
ll ans=0;
for(int i=0;i<=m+1;++i) {
ans=(ans+f[n][i])%mod;
}
printf("%lld\n",ans);
}
return 0;
}
洛谷 P13309
并非作业题。
主播发现一半的结论后开始乱搞,失败了,遂开题解。不会博弈论。
打表注意到 \(n\) 为奇数答案为中位数,否则要判断是两个中位数中的哪个。
给小于等于小的那个中位数的数设为 \(-1\),大于的设为 \(1\),结论是前缀和为 \(0\) 额位置数的奇偶性,奇数则为大的中位数,否则为小的中位数。
证明咕咕咕。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e5+5,INF=2e9,mod=1e9+7;
int t,n,m;
int a[N],all[N];
void sol() {
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]),all[i]=a[i];
}
sort(all+1,all+1+n);
if((n&1)||all[n/2]==all[n/2+1]) {
printf("%d\n",all[n/2+1]);return;
}
int sum=0,cnt=0;
for(int i=1;i<=n;++i) {
a[i]=(a[i]<=all[n/2])?-1:1;
sum+=a[i],cnt+=(!sum);
}
if(cnt&1) printf("%d\n",all[n/2]);
else printf("%d\n",all[n/2+1]);
}
int main()
{
scanf("%d",&t);
while(t--) {
sol();
}
return 0;
}
CF1616H
太神秘了,不过应该是好题。
首先显然是字典树题。
设 \(f_{u,v}\) 表示在子树 \(u\) 和 \(v\) 中选数(可以只选其中一个子树甚至啥都不选,方案是包含的),异或值两两不超过 \(x\) 的方案数。当前存在最高位限制。(其实思路还是套路的,一路顶着最高位限制算 DP 并顺便算了限制消除的方案。)
首先考虑 \(u=v\):
-
如果当前位 \(d\) 为 \(1\),那么从 \(f_{lu,ru}\) 转移过来,即可能选两个子树并顶到最高位限制。
-
如果当前位 \(d\) 为 \(0\),那么只可能选同一棵子树,即将 \(f_{lu,lu}\) 和 \(f_{ru,ru}\) 相加。
最后考虑 \(u\ne v\):
-
如果当前位 \(d\) 为 \(1\),则 \(u,v\) 的总共 \(4\) 个子树都有可能被选,由于状态要顶着最高位限制,于是方案数为 \(f_{lu,rv}\times f_{lv,ru}\)。
-
如果当前位 \(d\) 为 \(0\),那么 \(u,v\) 只能同时在左子树或者同时在右子树中选,方案数先算上 \(f_{lu,lv}+f_{ru,rv}\)。然后考虑子树 \(u,v\) 中也可能只选一个,其中左子树右子树可以自由选(没有最高位限制),总方案数再加上这些方案。
做完这些后你惊奇的发现每个点 \(i\) 只会在一对递归到的 \(f_{u,v}\) 中的 \(u\) 或者 \(v\) 中出现,所以复杂度是 \(O(n\log n)\) 的。
最后就是由于 \(f_{u,v}\) 中包含了空集的情况,所以得有些地方得减去 \(1\) 防止算重。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=2e5+5,INF=2e9,mod=998244353;
int t,n,x;
int a[N];
int tr[N*32][2],sz[N*32],idx=1;
ll pw[N];
void insert(int x) {
int u=1;++sz[1];
for(int i=30;i>=0;--i) {
int to=(x>>i)&1;
if(!tr[u][to]) tr[u][to]=++idx;
u=tr[u][to],++sz[u];
}
}
ll dfs(int d,int u,int v) {
if(!u||!v) return pw[sz[u|v]];//0留给空节点
if(u==v) {
if(d<0) return pw[sz[u]];
int lc=tr[u][0],rc=tr[u][1];
if((x>>d)&1) return dfs(d-1,lc,rc);
else return (dfs(d-1,lc,lc)+dfs(d-1,rc,rc)-1+mod)%mod;
}
else {
if(d<0) return pw[sz[u]+sz[v]];
int lu=tr[u][0],ru=tr[u][1],lv=tr[v][0],rv=tr[v][1];
if((x>>d)&1) return dfs(d-1,lu,rv)*dfs(d-1,lv,ru)%mod;
else {
ll res=(dfs(d-1,lu,lv)+dfs(d-1,ru,rv)-1+mod)%mod;
res=(res+(pw[sz[lu]]-1+mod)*(pw[sz[ru]]-1+mod))%mod;
res=(res+(pw[sz[lv]]-1+mod)*(pw[sz[rv]]-1+mod))%mod;
return res;
}
}
}
int main()
{
scanf("%d%d",&n,&x);pw[0]=1;
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);insert(a[i]);
pw[i]=pw[i-1]*2%mod;
}
printf("%lld\n",(dfs(30,1,1)-1+mod)%mod);
return 0;
}
UOJ805
考虑一个区间的合法条件。
- 区间长度为偶数。
- 问号全部换成左括号,然后将左括号视为 \(1\),右括号视为 \(-1\),所有前缀和均不小于 \(0\)。
- 问号全部换成右括号,然后将右括号视为 \(1\),左括号视为 \(-1\),所有后缀和均不小于 \(0\)。
知道这个后考虑预处理出每个 \(l\) 的最右边一个位置 \(p_l\) 满足前缀和正好为 \(-1\),即不合法的分界。同理预处理出每个 \(r\) 的最右边一个位置 \(q_r\) 满足后缀和正好为 \(-1\)。
于是条件转化成求满足 \(q_r<l,r<p_l\) 且 \(r-l+1\) 为偶数的 \((l,r)\) 对数。扫描线+树状数组即可。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e6+5,INF=2e9,mod=1e9+7;
int t,n,m;
char s[N];
int ls[N<<1],pre[N],suf[N],tr[2][N];
vector<int> e[N];
void add(int tr[],int u,int x) {
for(int i=u;i<=n;i+=i&(-i)) tr[i]+=x;
}
int query(int tr[],int u) {
int res=0;
for(int i=u;i!=0;i-=i&(-i)) res+=tr[i];
return res;
}
int main()
{
scanf("%s",s+1),n=strlen(s+1);
int nw=n;
for(int i=0;i<=n+n;++i) ls[i]=n+1;
for(int i=n;i>=1;--i) {
if(s[i]!=')') --nw,suf[i]=ls[nw];
else ++nw,ls[nw]=i;
}
for(int i=0;i<=n+n;++i) ls[i]=0;nw=n;
for(int i=1;i<=n;++i) {
if(s[i]!='(') --nw,pre[i]=ls[nw];
else ++nw,ls[nw]=i;
}
for(int i=1;i<=n;++i) {
if(s[i]!='(') e[pre[i]].epb(i);
}
ll ans=0;
for(int i=1;i<=n;++i) {
for(auto it:e[i-1]) add(tr[(it&1)^1],it,1);
if(s[i]!=')') {
ans+=query(tr[i&1],suf[i]-1)-query(tr[i&1],i-1);
}
}
printf("%lld\n",ans);
return 0;
}
NOI2525 D2T1
D1T1 太糖了,这个还稍微有点意思。做了 50min 交上去一遍过,疑似拿下了机房首 A,纪念一下。话说这题真卡常吗。
手玩 + 观察样例,注意到形如 \(110X\) 的变化一次会变成 \(X110\),也就是 \(0\) 会往右边移,直到移到底。
于是有了第一个结论,若存在 \(110\),则只需找到最左边 \(110\) 的位置即可求出答案。
写写写,然后样例没过。再次观察样例注意到,如果 \(110\) 不存在但是存在 \(101\),那么还是会有一次操作。
于是有了第二个结论,不存在 \(110\) 时,存在 \(101\) 则答案为 \(1\),否则为 \(0\)。
这个小样例也是纯神,几乎直接提示了做法。那么接下来只需要线段树搞一搞就行了。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
#define ls u<<1
#define rs u<<1|1
//#define ull unsigned long long
using namespace std;
const int N=4e5+5,INF=2e9,mod=1e9+7;
int c,t,n,q;
char s[N];int a[N];
struct node {
int l,r,len,tag,mi,mi2,has,has2;
int l1,l2,r1,r2;
}tr[N<<2];
void pushup(int u) {
tr[u].mi=min(tr[ls].mi,tr[rs].mi);
tr[u].mi2=min(tr[ls].mi2,tr[rs].mi2);
tr[u].has=tr[ls].has|tr[rs].has;
tr[u].has2=tr[ls].has2|tr[rs].has2;
int p=tr[ls].r;
if(tr[ls].len>=2) {
if(tr[ls].r2&&tr[ls].r1&&!tr[rs].l1) {
tr[u].mi=min(tr[u].mi,p+1);
}
if(!tr[ls].r2&&!tr[ls].r1&&tr[rs].l1) {
tr[u].mi2=min(tr[u].mi2,p+1);
}
if(tr[ls].r2&&!tr[ls].r1&&tr[rs].l1) {
tr[u].has|=1;
}
if(!tr[ls].r2&&tr[ls].r1&&!tr[rs].l1) {
tr[u].has2|=1;
}
tr[u].l1=tr[ls].l1,tr[u].l2=tr[ls].l2;
}
else tr[u].l1=tr[ls].l1,tr[u].l2=tr[rs].l1;
if(tr[rs].len>=2) {
if(tr[ls].r1&&tr[rs].l1&&!tr[rs].l2) {
tr[u].mi=min(tr[u].mi,p+2);
}
if(!tr[ls].r1&&!tr[rs].l1&&tr[rs].l2) {
tr[u].mi2=min(tr[u].mi2,p+2);
}
if(tr[ls].r1&&!tr[rs].l1&&tr[rs].l2) {
tr[u].has|=1;
}
if(!tr[ls].r1&&tr[rs].l1&&!tr[rs].l2) {
tr[u].has2|=1;
}
tr[u].r1=tr[rs].r1,tr[u].r2=tr[rs].r2;
}
else tr[u].r1=tr[rs].r1,tr[u].r2=tr[ls].r1;
}
void add(int u) {
tr[u].tag^=1;
tr[u].l1^=1,tr[u].l2^=1,tr[u].r1^=1,tr[u].r2^=1;
swap(tr[u].mi,tr[u].mi2),swap(tr[u].has,tr[u].has2);
}
void pushdown(int u) {
if(tr[u].tag) {
add(ls),add(rs),tr[u].tag=0;
}
}
void build(int u,int l,int r) {
tr[u].l=l,tr[u].r=r,tr[u].len=r-l+1;
tr[u].mi=tr[u].mi2=INF,tr[u].tag=0;
if(l==r) {
tr[u].l1=tr[u].r1=a[l];
return;
}
int mid=l+r>>1;
build(ls,l,mid),build(rs,mid+1,r);
pushup(u);
}
void update(int u,int l,int r) {
if(l<=tr[u].l&&tr[u].r<=r) {
return add(u);
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) update(ls,l,r);
if(r>mid) update(rs,l,r);
pushup(u);
}
void sol() {
scanf("%d%d",&n,&q);
scanf("%s",s+1);
for(int i=1;i<=n;++i) {
a[i]=s[i]-'0';
}
build(1,1,n);
ll ans=0;int l,r;
if(tr[1].mi>INF/2) ans^=tr[1].has;
else ans^=(n-tr[1].mi+1);
for(int i=1;i<=q;++i) {
scanf("%d%d",&l,&r);update(1,l,r);
if(tr[1].mi>INF/2) ans^=(i+1)*tr[1].has;
else ans^=1ll*(i+1)*(n-tr[1].mi+1);
}
printf("%lld\n",ans);
}
int main()
{
scanf("%d%d",&c,&t);
while(t--) {
sol();
}
return 0;
}
CF2122D
我的评价是,昨天 CF D 比 NOI 俩签字都难。
各种神秘最短路貌似只能做第一问。
想做第二问需要猜。猜测答案不会太大。事实上答案不会超过 \(3n\)。
知道了后直接设 \(f_{i,j}\) 表示时间 \(i\) 走到点 \(j\) 的最小等待时间。转移是 \(O(n^2+m)\) 的。
code
简洁!
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=5e3+5,INF=2e9,mod=1e9+7;
int t,n,m;
vector<int> e[N];
int f[N],g[N],deg[N];
void sol() {
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<=n;++i) {
e[i].clear();
}
for(int i=1;i<=m;++i) {
scanf("%d%d",&u,&v);
e[u].epb(v),e[v].epb(u);
}
for(int i=1;i<=n;++i) {
deg[i]=e[i].size(),f[i]=INF;
}
f[1]=0;
for(int i=0;;++i) {
for(int j=1;j<=n;++j) {
g[j]=f[j],++f[j];
}
for(int j=1;j<=n;++j) {
int to=e[j][i%deg[j]];
f[to]=min(f[to],g[j]);
}
if(f[n]<INF/2) {
printf("%d %d\n",i+1,f[n]);
return;
}
}
}
int main()
{
scanf("%d",&t);
while(t--) {
sol();
}
return 0;
}
CF2122E
都不会都不会都不会都不会都不会,我好菜呜呜呜呜呜呜呜呜呜呜。感觉 E>D 啊,为啥说 D>E。
首先这题显然和 \(a_{i+1}-b_i\) 有关。所以设 \(c_i=a_{i+1}-b_i\)。
考虑一个子区间。设 \(p\) 为最长的前缀满足对于 \(1\le i\le p\) 都有 \(c_i\ge 0\)。注意到 \([1,p]\) 是可以整个扔掉的。我们可以只关心以 \(p+1\) 开头的区间的合法性,即所有满足第一个 \(c_i<0\) 的区间的合法性。
设 \(f_{i,j}\ (j\le 0)\) 表示算到了 \(c_i\),从上一个 \(c_i<0\) 的位置开始 \(c_i\) 总和为 \(j\) 的方案数。转移平凡。
由于 \(-k\le j\le 0\),所以复杂度是 \(O(n^2k)\)。
code
有点细节的。
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=505,INF=2e9,mod=998244353;
int t,n,v;
int a[2][N];
int f[N][N];
void sol() {
scanf("%d%d",&n,&v);
for(int i=0;i<2;++i) {
for(int j=1;j<=n;++j) {
scanf("%d",&a[i][j]);
}
}
for(int i=0;i<=v;++i) f[1][i]=0;
f[1][v+1]=1;
for(int i=2;i<=n;++i) {
for(int j=0;j<=v+1;++j) f[i][j]=0;
for(int j=0;j<=v+1;++j) {
if(!f[i-1][j]) continue;
for(int k=0;k<=v;++k) {
int val,val2;//val 上大于下 val2 下大于上
if(a[0][i]==-1&&a[1][i-1]==-1) {
val=val2=v-k;
}
else if(a[0][i]==-1) {
val=(a[1][i-1]+k<=v)?1:0;
val2=(a[1][i-1]-k>0)?1:0;
}
else if(a[1][i-1]==-1) {
val=(a[0][i]-k>0)?1:0;
val2=(a[0][i]+k<=v)?1:0;
}
else {
val=val2=0;
if(a[0][i]-a[1][i-1]==k) val=1;
if(a[1][i-1]-a[0][i]==k) val2=1;
}
if(!k) val2=0;
if(j==v+1) {
f[i][v+1]=(f[i][v+1]+1ll*f[i-1][j]*val)%mod;
}
else if(j>=k) {
f[i][j-k]=(f[i][j-k]+1ll*f[i-1][j]*val)%mod;
}
f[i][k]=(f[i][k]+1ll*f[i-1][j]*val2)%mod;
}
}
}
int ans=0;
for(int i=0;i<=v+1;++i) {
ans=(ans+f[n][i])%mod;
}
if(a[0][1]==-1) ans=1ll*ans*v%mod;
if(a[1][n]==-1) ans=1ll*ans*v%mod;
printf("%d\n",ans);
}
int main()
{
scanf("%d",&t);
while(t--) {
sol();
}
return 0;
}
JOI 2018 Final 毒蛇越狱
首先考虑只有 0 和 ? 可以怎么做。考虑枚举 0 当成 ?,那么容斥+高维前缀和就做完了。
同理只有 1 和 ? 的话容斥+高维后缀和即可。
然后观察询问。根据鸽巢原理,01? 三个字符中出现最少的那个不会超过 \(6\) 次。那么考虑 0 最少就对 0 容斥,1 最少对 1 容斥,? 最少直接枚举即可。
时间复杂度 \(O(n2^n+q2^\frac{n}{3})\)。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=(1<<20)+5,INF=2e9,mod=1e9+7;
int t,n,m;
char s[N],re[22];int a[N];
int sm0[N],sm1[N];//前缀 后缀
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",s);int all=(1<<n)-1;
for(int i=0;i<(1<<n);++i) {
sm0[i]=sm1[i]=s[i]-'0';
}
for(int i=0;i<n;++i) {
for(int j=0;j<(1<<n);++j) {
if((j>>i)&1) {
sm0[j]+=sm0[j^(1<<i)];
}
else sm1[j]+=sm1[j^(1<<i)];
}
}
for(int i=1;i<=m;++i) {
scanf("%s",re);
int cnt0=0,cnt1=0,cntw=0,s0=0,s1=0,sx=0;
for(int j=0;j<n;++j) {
if(re[j]=='0') ++cnt0,s0|=(1<<(n-j-1));
else if(re[j]=='1') ++cnt1,s1|=(1<<(n-j-1));
else sx|=(1<<(n-j-1));
}
cntw=n-cnt0-cnt1;int ans=0;
if(cnt0<=cnt1&&cnt0<=cntw) {
for(int j=s0;;j=(j-1)&s0) {
if((cnt0-popcnt(j))&1) ans-=sm1[all^(j|sx)];
else ans+=sm1[all^(j|sx)];
if(!j) break;
}
}
else if(cnt1<=cnt0&&cnt1<=cntw) {
for(int j=s1;;j=(j-1)&s1) {
if((cnt1-popcnt(j))&1) ans-=sm0[j|sx];
else ans+=sm0[j|sx];
if(!j) break;
}
}
else {
for(int j=sx;;j=(j-1)&sx) {
ans+=(s[s1|j]-'0');
if(!j) break;
}
}
printf("%d\n",ans);
}
return 0;
}
AGC035F
小清新容斥题。
Q:主播主播你怎么把题解抄了一遍。
A:题解讲的很好了,而且主播也不会别的做法\yun。
我们注意到操作序列并不唯一对应一个矩阵。具体地, \(k_i=j-1,l_j=i\) 和 \(k_i=j,l_j=i-1\) 对应的矩阵相同。我们称这样的结构为一个拐点。
钦定了 \(i\) 个拐点的网格个数为:
钦定了 \(i\) 个拐点的操作序列个数为:
设 \(g_i\) 表示正好存在 \(i\) 个拐点的操作序列数量。二项式反演得:
答案即为:
开始推柿子:
(最后一步是二项式定理逆用。)
直接算就好了。复杂度 \(O(n\log n)\)。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=5e5+5,INF=2e9,mod=998244353;
int t,n,m;
ll fact[N],infact[N];
ll qpow(ll x,int y=mod-2) {
ll res=1;
while(y) {
if(y&1) res=res*x%mod;
x=x*x%mod,y>>=1;
}
return res;
}
void init(int n) {
fact[0]=infact[0]=1;
for(int i=1;i<=n;++i) fact[i]=fact[i-1]*i%mod;
infact[n]=qpow(fact[n]);
for(int i=n;i>=1;--i) infact[i-1]=infact[i]*i%mod;
}
ll C(int x,int y) {
if(x<0||y<0||x<y) return 0;
return fact[x]*infact[y]%mod*infact[x-y]%mod;
}
int main()
{
scanf("%d%d",&n,&m);
init(max(n,m));int lim=min(n,m);ll ans=0;
for(int i=0;i<=lim;++i) {
ll val=C(n,i)*C(m,i)%mod*fact[i]%mod*qpow(n+1,m-i)%mod*qpow(m+1,n-i)%mod;
if(i&1) val=mod-val;
ans=(ans+val)%mod;
}
printf("%lld\n",ans);
return 0;
}
洛谷 P5643
首先 \(min-max\) 容斥做一个转化,问题变成求到达某个点集某个点的期望步数。
设 \(f_u\) 表示从 \(u\) 开始的期望步数。对于不属于点集的点 \(u\) 我们有:
考虑经典的树上高消,设 \(f_u=k_u f_{fa_u}+b_u\) 并将 \(f_v\) 展开成这样,推柿子,我们得到:
于是 \(f_{fa_i}\) 前面一坨即为 \(k_u\),后面的即为 \(b_u\)。这两坨东西和 \(fa_u\) 没有关系,所以一次 \(dfs\) 即可求出所有的 \(k_u\) 和 \(b_u\),最后 \(f_{rt}\) 即为 \(b_{rt}\)。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=20,M=(1<<18)+5,INF=2e9,mod=998244353;
int t,n,q,rt;vector<int> e[N];
int on=0;int in[N];
ll ans[M],k[N],b[N];
ll qpow(ll x,int y=mod-2) {
ll res=1;
while(y) {
if(y&1) res=res*x%mod;
x=x*x%mod,y>>=1;
}
return res;
}
void dfs(int u,int fa) {
if((on>>u)&1) {
k[u]=b[u]=0;return;
}
ll sk=in[u],sb=in[u];
for(auto to:e[u]) {
if(to==fa) continue;dfs(to,u);
sk=(sk-k[to]+mod)%mod;
sb=(sb+b[to])%mod;
}
k[u]=qpow(sk),b[u]=sb*k[u]%mod;
}
void init() {
for(on=1;on<(1<<n);++on) {
dfs(rt,-1),ans[on]=b[rt];
if(!(popcnt(on)&1)) ans[on]=mod-ans[on];
}
for(int i=0;i<n;++i) {
for(int j=0;j<(1<<n);++j) {
if((j>>i)&1) {
ans[j]=(ans[j]+ans[j^(1<<i)])%mod;
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&q,&rt),--rt;
int u,v;
for(int i=1;i<=n-1;++i) {
scanf("%d%d",&u,&v),--u,--v;
e[u].epb(v),e[v].epb(u);
}
for(int i=0;i<n;++i) {
in[i]=e[i].size();
}
init();int sz,x,s;
while(q--) {
scanf("%d",&sz);s=0;
while(sz--) {
scanf("%d",&x),s|=(1<<(x-1));
}
printf("%lld\n",ans[s]);
}
return 0;
}
AGC038C
很平凡的推柿子题,自己盯出来了。
接下来这一步将 \(\mu(d)\) 的求和移到前面容易直接盯出最终柿子。设 \(S(i)=\sum_{j=1}^{n}A_j[i|A_j]\),那么原式等于:
不言而喻,一目了然!
最后要注意的是,由于我们求的是 \(1\le j\le n\) 的情况,需要转换成 \(i+1\le j\le n\) 的答案。这样是容易的,设我们求出的答案为 \(ans\),那么真正的答案即为 \(\frac{ans-\sum A_i}{2}\)。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e6+5,INF=2e9,mod=998244353;
int t,n,q,x;
int a[N];ll inv[N],_sm[N],sm[N];
int primes[N],st[N],mu[N],len=0;
void init(int n) {
inv[1]=1,mu[1]=1;
for(int i=2;i<=n;++i) {
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
if(!st[i]) {
primes[++len]=i,mu[i]=-1;
}
for(int j=1;j<=len&&primes[j]<=n/i;++j) {
st[i*primes[j]]=1;
if(i%primes[j]==0) {
mu[i*primes[j]]=0;
continue;
}
else mu[i*primes[j]]=-mu[i];
}
}
for(int i=1;i<=n;++i) {
for(int j=i;j<=n;j+=i) {
sm[i]=(sm[i]+_sm[j])%mod;
}
}
}
int main()
{
scanf("%d",&n);int mx=0;
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]),mx=max(mx,a[i]);
_sm[a[i]]=(_sm[a[i]]+a[i])%mod;
}
init(mx);ll ans=0;
for(int i=1;i<=mx;++i) {
ll res=0;
for(int j=i,k=1;j<=mx;j+=i,++k) {
res=(res+sm[j]*sm[j]%mod*inv[k])%mod;
}
ans=(ans+res*mu[i]-_sm[i]+mod+mod)%mod;
}
printf("%lld\n",ans*inv[2]%mod);
return 0;
}
ARC101E
做过/yun。
首先我们有一个显然的 \(O(n^3)\) DP,设 \(f_{u,j}\) 表示点 \(u\) 的子树内还有 \(j\) 个点未匹配的方案数,转移平凡。
优化到 \(O(n^2)\) 考虑容斥,如果我们钦定了 \(x\) 条边不被覆盖,那么贡献系数是 \((-1)^x\)。
我们还知道一个大小为 \(2x\) 的连通块任意匹配的方案数 \(g(x)=\prod_{i=1}^{x}(2i-1)\)。
于是我们又有了一个 DP。设 \(f_{u,j}\) 表示 \(u\) 所在的连通块大小为 \(j\) 的方案数。转移考虑和子树的边是否断掉即可。
最后的答案为 \(\sum_{i=1}^{n}f_{1,i}\times g(i)\)。
code
#include <bits/stdc++.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=5e3+5,INF=2e9,mod=1e9+7;
int n,m;
vector<int> e[N];
ll dp[N][N],w[N],sz[N];
void dfs(int u,int fa) {
sz[u]=1,dp[u][1]=1;
for(auto to:e[u]) {
if(to==fa) continue;
dfs(to,u);
vector<int> f(sz[u]+sz[to]+1,0);
for(int i=1;i<=sz[u];++i) {
for(int j=1;j<=sz[to];++j) {
f[i+j]=(f[i+j]+dp[u][i]*dp[to][j])%mod;
f[i]=(f[i]-dp[u][i]*dp[to][j]%mod*w[j]%mod+mod)%mod;
}
}
sz[u]+=sz[to];
for(int i=1;i<=sz[u];++i) {
dp[u][i]=f[i];
}
}
}
int main() {
scanf("%d",&n);
int u,v;
for(int i=1;i<=n-1;++i) {
scanf("%d%d",&u,&v);
e[u].epb(v),e[v].epb(u);
}
w[0]=1;
for(int i=2;i<=n;i+=2) {
w[i]=w[i-2]*(i-1)%mod;
}
dfs(1,-1);
ll ans=0;
for(int i=1;i<=n;++i) {
ans=(ans+dp[1][i]*w[i])%mod;
}
printf("%lld\n",ans);
return 0;
}
ARC093D
小清新容斥题。
首先注意到我们可以钦定选手 \(1\) 固定在位置 \(1\),答案最后乘上 \(2^n\) 即可。
设 \(G_i=\{p_{2^i+1},p_{2^i+2}...p_{2^{i+1}}\}\),那么和选手 \(1\) 交手的就是每个集合中的最小值。
考虑将所有的 \(A_i\) 从大到小插入,再考虑容斥,设 \(f_{i,S}\) 表示考虑到 \(A_i\),目前 \(S\) 中的集合都已经被分配满的方案数,且每个被分配的集合的最小值都出现在 \(A_i\) 中。注意到 \(S\) 的大小正好是已经分配的人数。
转移考虑是否让 \(A_i\) 被钦定为某个未分配集合的最小值。
-
如果不钦定,那么直接就有 \(f_{i,S}\gets f_{i-1,S}\)。
-
如果钦定,设钦定了其作为第 \(k\) 个集合的最小值(集合 \(k\) 尚未分配),那么在加入 \(A_i\) 的同时还需要选出 \(2^k-1\) 个编号比他大的未分配的人一起放进去,同时这总共 \(2^k\) 个人还可以任意排列,所以我们有 \(f_{i,S|2^k}\gets f_{i-1,S}\times \binom{2^n-S-A_i}{2^k-1}\times (2^k)!\)
最后答案为 \(\sum_{S=0}^{2^n-1}f_{m,S}\times (2^n-S-1)!\times (-1)^{popcount(S)}\)。其中 \((2^n-S-1)!\) 为将剩下的未分配的人任意排列的方案数, \((-1)^{popcount(S)}\) 为容斥系数。
code
好写,清新。
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=18,M=(1<<16)+5,INF=2e9,mod=1e9+7;
int t,n,m;
int a[N];
ll f[N][M],fact[M],infact[M];
ll qpow(ll x,int y=mod-2) {
ll res=1;
while(y) {
if(y&1) res=res*x%mod;
x=x*x%mod,y>>=1;
}
return res;
}
void init(int n) {
fact[0]=infact[0]=1;
for(int i=1;i<=n;++i) {
fact[i]=fact[i-1]*i%mod;
}
infact[n]=qpow(fact[n]);
for(int i=n;i>=1;--i) {
infact[i-1]=infact[i]*i%mod;
}
}
ll C(int x,int y) {
if(x<0||y<0||x<y) return 0;
else return fact[x]*infact[y]%mod*infact[x-y]%mod;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
scanf("%d",&a[i]);
}
int all=(1<<n);
sort(a+1,a+1+m,greater<int>());
f[0][0]=1;init(all);
for(int i=1;i<=m;++i) {
for(int j=0;j<all;++j) {
f[i][j]=(f[i][j]+f[i-1][j])%mod;
for(int k=0;k<n;++k) {
if(!((j>>k)&1)) {
int v=(1<<k),ls=all-a[i]-j;
f[i][j|v]=(f[i][j|v]+f[i-1][j]*C(ls,v-1)%mod*fact[v])%mod;
}
}
}
}
ll ans=0;
for(int i=0;i<all;++i) {
if(popcnt(i)&1) ans=(ans-f[m][i]*fact[all-i-1]%mod+mod)%mod;
else ans=(ans+f[m][i]*fact[all-i-1])%mod;
}
printf("%lld\n",ans*all%mod);
return 0;
}
洛谷 P3524
咋克精选,有意思的构造题。
每次选两个之间没有边的点对删去,能删就删。而每次删的两个点至少有一个在团外。
至多删 \(\frac{n}{3}\) 次,团外的点会全部删去,而团内至少还有 \(\frac{n}{3}\) 个点。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=3e3+5,INF=2e9,mod=1e9+7;
int t,n,m;
int e[N][N],vis[N],cnt=0;
inline void read(int &x) {
int f=1;x=0;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=(x<<3)+(x<<1)+c-48;
c=getchar();
}
x*=f;
}
int main()
{
read(n),read(m);
int u,v;
for(int i=1;i<=m;++i) {
read(u),read(v);
e[u][v]=e[v][u]=1;
}
for(int i=1;i<=n;++i) {
if(vis[i]) continue;
for(int j=i+1;j<=n;++j) {
if(!e[i][j]&&!vis[j]) {
vis[i]=vis[j]=1;
break;
}
}
}
int cnt=0;
for(int i=1;i<=n;++i) {
if(!vis[i]) {
printf("%d ",i),++cnt;
if(cnt==n/3) break;
}
}
puts("");
return 0;
}
CODE FESTIVAL 2017 Final F
抽象构造。题解没太看懂,自己大概糊了一个。
首先考虑每个数出现 \(k\) 次这一条件。得到 \(\frac{n(n-1)}{2}=\frac{nk(k-1)}{2}\),即 \(n=k(k-1)+1\)。
然后考虑钦定前 \(k\) 个纸都包含 \(1\)。
接下来的思路不好描述,可以见代码。大致是剩下的 \((k-1)^2\) 张纸分成 \(k-1\) 组,每组内部再钦定一个都包含的数,再保证剩下的两两不交。跨组之间为保证两两交集也为 \(1\),还需要对每列进行神秘的位移。
这个构造要求了 \(k-1\) 为质数,于是 \(k\) 取 \(38\)。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=2e3+5,INF=2e9,mod=1e9+7;
int k=38,n=(k-1)*k+1;
int ans[N][40];
int main()
{
for(int i=1;i<=k;++i) {
ans[i][1]=1;
for(int j=2;j<=k;++j) {
ans[i][j]=(k-1)*(i-1)+j;
}
}
for(int i=0;i<k-1;++i) {
for(int l=1;l<=k-1;++l) {
ans[k+i*(k-1)+l][1]=2+i;
}
for(int j=2;j<=k;++j) {
for(int l=1;l<=k-1;++l) {
ans[k+i*(k-1)+l][j]=k+(k-1)*(j-2)+(l-1+i*(j-2))%(k-1)+1;
}
}
}
printf("%d %d\n",n,k);
for(int i=1;i<=n;++i) {
for(int j=1;j<=k;++j) {
printf("%d ",ans[i][j]);
}
puts("");
}
return 0;
}
QOJ9737
还是决策单调性优化 DP 题。
但是直接上板子是不太对的,注意到 \(w(l,r)\) 不满足四边形不等式。
但是如果魔改一下, \(w'(l,r)=p-s+\frac{\sum_{i=l}^{r}a_i-\sum_{i=1}^{p}b_i}{b_{p+1}}\) 就满足四边形不等式了。拿这个玩意跑二分队列即可。
时间复杂度 \(O(n\log^2 n)\)。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=5e5+5,INF=2e9,mod=1e9+7;
int T,n,m,c;
ll a[N],b[N],sa[N],sb[N],f[N];
int q[N],k[N],h=1,t=0;
double get1(int l,int r) {
ll sm=sa[r]-sa[l];
int p=upper_bound(sb+1,sb+1+m,sm)-sb;
if(p==m+1) return f[l]+p-1-c;
else return f[l]+p-1-c+1.0*(sm-sb[p-1])/b[p];
}
ll get2(int l,int r) {
ll sm=sa[r]-sa[l];
int p=upper_bound(sb+1,sb+1+m,sm)-sb;
return f[l]+p-1-c;
}
int binary(int x,int y) {
int l=y,r=n,res=n+1;
while(l<=r) {
int mid=l+r>>1;
if(get1(x,mid)<get1(y,mid)) {
res=mid,r=mid-1;
}
else l=mid+1;
}
return res;
}
void sol() {
scanf("%d%d%d",&n,&m,&c);
for(int i=1;i<=n;++i) {
scanf("%lld",&a[i]);
sa[i]=sa[i-1]+a[i];
}
sa[n+1]=sa[n];
for(int i=1;i<=m;++i) {
scanf("%lld",&b[i]);
sb[i]=sb[i-1]+b[i];
}
f[0]=0,h=t=1,q[t]=0;
for(int i=1;i<=n;++i) {
while(h<t&&k[h]<=i) ++h;
f[i]=get2(q[h],i);
while(h<t&&get1(i,k[t-1])>get1(q[t],k[t-1])) --t;
k[t]=binary(q[t],i),q[++t]=i;
}
printf("%lld\n",f[n]);
}
int main()
{
scanf("%d",&T);
while(T--) {
sol();
}
return 0;
}
洛谷 P5892
假设经过区间的总长度为 \(len\),那么答案即为区间内的前 \(d-len\) 大之和。
考虑枚举左端点,发现最优的右端点具有决策单调性,直接上分治+主席树即可。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e5+5,INF=1e9,mod=1e9+7;
int t,n,s,d;
int a[N],all[N],rt[N],idx=0,Len=0;
ll ans[N],Ans=0;
struct tree {
int l,r,cnt;ll sum;
}tr[N*20];
inline void pushup(int p) {
tr[p].cnt=tr[tr[p].l].cnt+tr[tr[p].r].cnt;
tr[p].sum=tr[tr[p].l].sum+tr[tr[p].r].sum;
}
int insert(int p,int l,int r,int ps) {
int q=++idx;tr[q]=tr[p];
if(l==r) {
++tr[q].cnt,tr[q].sum+=all[l];
return q;
}
int mid=l+r>>1;
if(ps<=mid) tr[q].l=insert(tr[p].l,l,mid,ps);
else tr[q].r=insert(tr[p].r,mid+1,r,ps);
pushup(q);return q;
}
ll query(int p,int q,int l,int r,int k) {
if(!k) return 0;
if(l==r) return 1ll*min(k,tr[p].cnt-tr[q].cnt)*all[l];
int mid=l+r>>1,Cnt=tr[tr[p].r].cnt-tr[tr[q].r].cnt;
if(Cnt<=k) {
return tr[tr[p].r].sum-tr[tr[q].r].sum+query(tr[p].l,tr[q].l,l,mid,k-Cnt);
}
else return query(tr[p].r,tr[q].r,mid+1,r,k);
}
void solve(int l,int r,int ql,int qr) {
if(l>r||ql>qr) return;
int mid=l+r>>1;ll id=ql;
for(int i=ql;i<=qr;++i) {
int len=2*(s-mid)+i-s;
ll val=(d<=len)?0:query(rt[i],rt[mid-1],1,Len,d-len);
if(val>ans[mid]) ans[mid]=val,id=i;
}
solve(l,mid-1,ql,id),solve(mid+1,r,id,qr);
}
void clear() {
for(int i=1;i<=idx;++i) {
tr[i].l=tr[i].r=tr[i].cnt=tr[i].sum=0;
}
idx=0;
}
void sol() {
for(int i=1;i<=n;++i) {
rt[i]=insert(rt[i-1],1,Len,a[i]);
ans[i]=0;
}
solve(1,s,s,n);
for(int i=1;i<=s;++i) {
Ans=max(Ans,ans[i]);
}
}
int main()
{
scanf("%d%d%d",&n,&s,&d),++s;
for(int i=1;i<=n;++i) {
scanf("%d",&a[i]);all[i]=a[i];
}
sort(all+1,all+1+n),Len=unique(all+1,all+1+n)-all-1;
for(int i=1;i<=n;++i) {
a[i]=lower_bound(all+1,all+1+Len,a[i])-all;
}
sol(),s=n-s+1,reverse(a+1,a+1+n),clear(),sol();
printf("%lld\n",Ans);
return 0;
}
AGC050A
连边 \(x\to 2x\) 和 \(x\to 2x+1\)。
从任意一点 \(x\) 开始走 \(10\) 步可以走到所有的 \(1024x+y,y\in [0,2023)\)。
在连边时将原数对 \(N\) 取模后加 \(1\),由于 \((1024x+y)\mod n+1\) 包含了所有 \([1,N]\) 中的所有数,所以构造成立。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=2e5+5,INF=2e9,mod=1e9+7;
int t,n,m;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) {
printf("%d %d\n",i*2%n+1,(i*2+1)%n+1);
}
return 0;
}
牛客多校 Day 3 H
题意
给你一个树,树的根节点 \(1\) 有一个棋子。
接下来有 \(k\) 个不重叠的区间 \([l_i,r_i]\),在区间 \([l_i,r_i]\) 时点 \(u_i\) 会被标记为目标。你可以在任意的时刻切断树中任意数量的边(永久删除)。
对于每个时刻,棋子会进行如下运动:
- 如果目标存在,且 \(u_i\) 和当前棋子所在点连通且不重合,那么向 \(u_i\) 的方向前进一条边。
求割边使得棋子到达任意一个目标点的最短时刻。若无法到达输出 \(-1\)。
sol
很巧妙的题,和前两场的垃圾题质量不是一个量级,不愧是 cfz 出题组。
注意到任意时刻根节点 \(1\) 能到达的点是一个包含 \(1\) 的极大连通块。
考虑动态地扩展这个连通块。具体地,时间段 \([l_i,r_i]\) 对应了我们要令连通块向 \(u_i\) 方向扩展 \(r_i-l_i+1\) 步。如果注意到 \(u_i\) 被连通块覆盖了就可以输出答案了。
具体实现考虑倍增,从 \(u\) 向上跳找到第一个在连通块内的点再往下扩展(往下扩展的部分可以选择 dfs+二分 也可以选择再倍增一次找到扩展的链的另一个端点,后者更好写)。
code
#include <bits/stdc++.h>
//#include <windows.h>
//taskkill /f /im 未命名1.exe
#define ED cerr<<endl;
#define TS cerr<<"I AK IOI"<<endl;
#define cr(x) cerr<<x<<endl;
#define cr2(x,y) cerr<<x<<" "<<y<<endl;
#define cr3(x,y,z) cerr<<x<<" "<<y<<" "<<z<<endl;
#define cr4(x,y,z,w) cerr<<x<<" "<<y<<" "<<z<<" "<<w<<endl;
#define pr(a,l,r) for(int i=l;i<=r;++i) cerr<<a[i]<<' ';puts("");
#define popcnt __builtin_popcount
#define all(s) s.begin(),s.end()
#define bstring basic_string
//#define add(x,y) (x+=y)%=mod
#define pii pair<int,int>
#define epb emplace_back
#define pb push_back
#define mk make_pair
#define ins insert
#define fi first
#define se second
#define ll long long
//#define ull unsigned long long
using namespace std;
const int N=1e6+5,INF=2e9,mod=1e9+7;
int t,n,k;
vector<int> e[N];
int p[21][N],dep[N],f[N];
void dfs(int u) {
for(int i=1;i<21;++i) {
p[i][u]=p[i-1][p[i-1][u]];
}
for(auto to:e[u]) {
dep[to]=dep[u]+1,dfs(to);
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=2;i<=n;++i) {
scanf("%d",&p[0][i]);
e[p[0][i]].epb(i),f[i]=-1;
}
dfs(1);
int l,r,u,ok=0,ans=0;f[0]=f[1]=1;
for(int i=1;i<=k;++i) {
scanf("%d%d%d",&u,&l,&r);
if(ok) continue;
if(f[u]!=-1) {
printf("%d\n",l);return 0;
}
int t=u,_u=u;
for(int j=20;j>=0;--j) {
if(f[p[j][t]]==-1) t=p[j][t];
}
if(f[t]==-1) t=p[0][t];
for(int j=20;j>=0;--j) {
if(dep[p[j][u]]-dep[t]>r-l+1) u=p[j][u];
}
if(dep[u]-dep[t]>r-l+1) u=p[0][u];
for(int v=u;v!=t;v=p[0][v]) {
f[v]=l+dep[u]-dep[t]-1;
}
if(f[_u]!=-1) ans=f[_u],ok=1;
}
if(!ok) puts("-1");
else printf("%d\n",ans);
return 0;
}

浙公网安备 33010602011771号