IOI 2021 集训队作业瞎做 Part 2
UH [todo]
在这里放个 [todo] 是来证明我学傻了的。
首先显然可以转化成二分图最小点覆盖集(前缀和后缀连边)。
(可能这玩意并不叫最小覆盖点集,意会就好
然后根据某定理,这个大小等于最大匹配大小。
至于构造方案,对于一条匹配边,两点肯定至少选一个(否则这条边就没被覆盖),而且只能选一个(否则选择点数就大于最大匹配)。而对于不在匹配中的点,不会被选(同理)。
那么对于每条未匹配边都变成了一个 2-SAT 的限制。
时间复杂度就那样。
这显然不是最优解法,等以后脑子清醒了再来填坑。
upd:草。
对于每条未匹配边,两点至少一个是匹配点。若只有属于左半部分的那个是匹配点,显然应该选那个匹配点。
选了那个点之后,与那个点匹配的点(是个右半部分的)也就不能用了,可能会导致一些新的未匹配边只能选左边。
不过这是一个 DFS 能搞定的事。
最后剩下一些没考虑的匹配边和未匹配边。此时每条未匹配边的右半部分都是可用的匹配点,所以剩下的所有匹配边都取右边点就行了。
SD
即等价于选一堆 \(2^x3^y\),要求 \(x_1\ge x_2\) 时必须要 \(y_1<y_2\)。
对于 \(n=1\) 显然。
对于 \(n\) 是偶数,可以递归到 \(n/2\),并将 \(n/2\) 的拆分全部乘 \(2\)。显然这里面的所有数会比前面所有数的 \(2\) 的次数大。
对于 \(n\) 是奇数,可以递归到 \((n-3^k)/2\),其中 \(3^k\le n\) 且 \(k\) 最大。注意到 \((n-3^k)/2<3^k\),所以这个数会比后面所有数的 \(3\) 的次数要大。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
ll n,tmp[maxn];
int tl;
void work(ll n,ll pr){
if(!n) return;
if(n<=3) return tmp[++tl]=pr*n,void();
if(n%2==0) return work(n/2,pr*2);
else{
ll t=1;
while(t<=n) t*=3;
t/=3;
return tmp[++tl]=pr*t,work(n-t,pr);
}
}
void solve(){
n=read();
tl=0;
work(n,1);
printf("%d\n",tl);
FOR(i,1,tl) printf("%lld ",tmp[i]);
puts("");
}
int main(){
freopen("distribution.in","r",stdin);
freopen("distribution.out","w",stdout);
int T=read();
while(T--) solve();
}
NJ [todo]
度数不超过 \(3\),显然两点间最大流也不超过 \(3\)。
两点最大流 \(>0\),当且仅当一开始联通。这个对数随便算。
两点最大流 \(>1\),当且仅当一开始在同一个双连通分量里。这个对数随便算。
两点最大流 \(>2\),当且仅当任意枚举一条边断掉,这两点都在同一双连通分量里。这个的算法,可以枚举断边,然后将每个点依次所在的双连通分量编号序列哈希(可能有其它判相等的方法,问题不大)。
有了这三个数,答案也随便算。
时间复杂度 \(O((n+m)^2)\)。
很没意思,代码先咕着。
ID
考虑分治。
一开始是个大图形,按照两点在多边形边上距离最远的边(下称直径)分开成两个小图形。这样两边的点数不会差太多,递归层数也就不会太多。
对于两点在一边的询问,递归下去求解。否则,两点的路径一定经过直径两端点中的至少一个。以这两个点为起点分别 BFS。
时间复杂度 \(O(n\log n)\)。
(好像还要稍微证一下每次两边不会差的太远?)
随便选择一个点 \(i\),考虑其所有出边指向的点(包括多边形边),并在两个半周上各选择距离最远的点 \(j,k\)。
若 \(j,k\) 之间没有连边,则 \(i,j,\dots,k\) 这个多边形没法被剖分(\(\dots\) 表示 \(j,k\) 之间的点)。所以 \(j,k\) 有连边。
若 \(i\) 在劣弧 \(jk\) 上(感性理解下劣弧的意思),那么忽略劣弧 \(jk\) 间的所有点,接着考虑(注意后面的过程中距离仍然是原多边形,即这些被忽略的点也计入距离,只是不再作为 \(i,j,k\) 出现)。
否则,\(ij,jk,ki\) 的距离之和就是点数,最长的一条肯定不小于 \(\frac{1}{3}\) 倍点数。
注意,虽然忽略了一些点,每次仍然是可以找到 \(j,k\) 的,因为每次只删劣弧(且劣弧端点没有被删),所以与其相邻的两点分别在两个半周上。
所以递归层数不超过 \(\log_{1.5}n\)。
我目前只会构造一层使得分成一边 \(\frac{1}{3}\) 一边 \(\frac{2}{3}\),但是到下一层好像就会分得飞快。反正就是不会卡满(
(有更紧的上界欢迎来 D /kel)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef pair<PII,int> PIII;
const int maxn=222222,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,m,ans[maxn],id[maxn],el,head[maxn],to[maxn],nxt[maxn],dis[2][maxn],que[maxn],h,r,at[maxn];
vector<int> v;
vector<PII> e;
vector<PIII> q;
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
to[++el]=u;nxt[el]=head[v];head[v]=el;
}
void build(vector<int> &v,vector<PII> &e){
FOR(i,0,(int)v.size()-2) add(v[i],v[i+1]);
add(v.back(),v.front());
FOR(i,0,(int)e.size()-1) add(e[i].first,e[i].second);
}
void clear(vector<int> &v){
FOR(i,1,el) to[i]=nxt[i]=0;
FOR(i,0,(int)v.size()-1) head[v[i]]=0;
el=0;
}
void bfs(int s,vector<int> &v,int *d){
FOR(i,0,(int)v.size()-1) d[v[i]]=-1;
d[s]=0;
que[h=r=1]=s;
while(h<=r){
int u=que[h++];
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(d[v]==-1) d[v]=d[u]+1,que[++r]=v;
}
}
}
void solve(vector<int> &v,vector<PII> &e,vector<PIII> &q){
if((int)v.size()<=3){
FOR(i,0,(int)q.size()-1) ans[q[i].second]=q[i].first.first!=q[i].first.second;
return;
}
vector<int> v1,v2;
vector<PII> e1,e2;
vector<PIII> q1,q2;
FOR(i,0,(int)v.size()-1) id[v[i]]=i;
int U=0,V=0,D=0;
FOR(i,0,(int)e.size()-1){
int uu=e[i].first,vv=e[i].second,d=abs(id[uu]-id[vv]);
d=min(d,(int)v.size()-d);
if(d>D) U=uu,V=vv,D=d;
}
build(v,e);
bfs(U,v,dis[0]);
bfs(V,v,dis[1]);
clear(v);
int cur=1;
FOR(i,0,(int)v.size()-1){
at[v[i]]=0;
if(v[i]==U || v[i]==V){
cur=3-cur;
v1.PB(v[i]);
v2.PB(v[i]);
}
else{
at[v[i]]=cur;
if(cur==1) v1.PB(v[i]);
else v2.PB(v[i]);
}
}
FOR(i,0,(int)e.size()-1){
int u=e[i].first,v=e[i].second;
assert(!at[u] || !at[v] || at[u]==at[v]);
if(at[u]==1 || at[v]==1) e1.PB(e[i]);
else if(at[u]==2 || at[v]==2) e2.PB(e[i]);
}
FOR(i,0,(int)q.size()-1){
int u=q[i].first.first,v=q[i].first.second;
if(at[u]!=at[v]) ans[q[i].second]=min(dis[0][u]+dis[0][v],dis[1][u]+dis[1][v]);
else if(!at[u]) ans[q[i].second]=u!=v;
else if(at[u]==1) q1.PB(q[i]);
else q2.PB(q[i]);
}
solve(v1,e1,q1);
solve(v2,e2,q2);
}
int main(){
freopen("distance.in","r",stdin);
freopen("distance.out","w",stdout);
n=read();
FOR(i,1,n-3){
int u=read(),v=read();
e.PB(MP(u,v));
}
m=read();
FOR(i,1,m){
int u=read(),v=read();
q.PB(MP(MP(u,v),i));
}
FOR(i,1,n) v.PB(i);
solve(v,e,q);
FOR(i,1,m) printf("%d\n",ans[i]);
}
KA [todo]
好不可做啊,感觉除了搜没啥做法吧???
题面太难看了,所以写个 [todo] 以免以后再看自闭。
IL
普及难度。
枚举最高峰的位置 \(i\),二分高度 \(mid\),然后判断给出的石头数是否足够。
注意到最高峰左边要加到 \(mid-1\),再左边要加到 \(mid-2\),直到一个本来就足够的地方为止,再左边就不用操作。
设这个位置是 \(j\),那么有 \(a_j+i-j\ge mid\),即 \(a_j-j\ge mid-i\)。可以二分。
右边同理,然后加的石头数就很好算了。
时间复杂度 \(O(n\log n\log a)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,a[maxn],st1[18][maxn],st2[18][maxn],ans,p[maxn],s[maxn];
ll k,pre[maxn];
inline ll sum(int l,int r){return 1ll*(l+r)*(r-l+1)/2;}
int main(){
// freopen("landscape.in","r",stdin);
// freopen("landscape.out","w",stdout);
n=read();k=read();
FOR(i,1,n) a[i]=read(),pre[i]=pre[i-1]+a[i];
p[0]=s[n+1]=-1e9;
FOR(i,1,n){
st1[0][i]=a[i]-i;
st2[0][i]=a[i]+i;
p[i]=max(p[i-1],a[i]-i);
}
ROF(i,n,1) s[i]=max(s[i+1],a[i]+i);
FOR(i,1,17) FOR(j,1,n-(1<<i)+1){
st1[i][j]=max(st1[i-1][j],st1[i-1][j+(1<<(i-1))]);
st2[i][j]=max(st2[i-1][j],st2[i-1][j+(1<<(i-1))]);
}
FOR(i,1,n){
int l=a[i],r=min(p[i]+i,s[i]-i);
while(l<r){
int mid=(l+r+1)>>1;
int lft=i,rig=i;
ROF(j,17,0) if(lft>(1<<j) && st1[j][lft-(1<<j)]<mid-i) lft-=1<<j;
ROF(j,17,0) if(rig+(1<<j)<=n && st2[j][rig+1]<mid+i) rig+=1<<j;
lft=min(lft+1,i);
rig=max(rig-1,i);
if(sum(mid-i+lft,mid)+sum(mid+i-rig,mid)-mid-(pre[rig]-pre[lft-1])<=k) l=mid;
else r=mid-1;
}
ans=max(ans,l);
}
printf("%d\n",ans);
}
LL [todo]
我现在就去杀了出题人全家,别拦我。
首先,你需要看到坐标只有 \(1000\) 这个范围。(官方题解把这一步叫 key observation,看起来是真的。)
然后跟我一起问候出题人吧。
GL
好无脑的一题。然后我居然还想了好久。
把所有路径按长度从大到小排序,一个一个加入。
一开始把所有点颜色都看成一样的一种。每次加入一个路径,先判断原来这条路径是否同色,然后染成新的颜色。
直接树剖可以 \(O(n\log^2 n)\)。
虽然很没意思但还是写了。
好像有更牛逼的做法?以 后 再 说。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=444444,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
struct hhh{
int u,v,w;
bool operator<(const hhh &h)const{return w>h.w;}
}h[maxn];
int n,m,el,head[maxn],to[maxn],nxt[maxn],fa[maxn],dep[maxn],sz[maxn],dfn[maxn],cnt,son[maxn],top[maxn],col[maxn],cov[maxn],cc;
char op[10];
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
void dfs1(int u,int f){
dep[u]=dep[fa[u]=f]+1;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f) continue;
dfs1(v,u);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
sz[u]++;
}
void dfs2(int u,int topf){
top[u]=topf;
dfn[u]=++cnt;
if(son[u]) dfs2(son[u],topf);
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==son[u] || v==fa[u]) continue;
dfs2(v,v);
}
}
int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
inline void pushup(int o){
col[o]=col[o<<1]==col[o<<1|1]?col[o<<1]:-1;
}
inline void setcov(int o,int c){
col[o]=cov[o]=c;
}
inline void pushdown(int o){
if(col[o]!=-1){
setcov(o<<1,col[o]);
setcov(o<<1|1,col[o]);
col[o]=-1;
}
}
void build(int o,int l,int r){
if(l==r) return col[o]=0,cov[o]=-1,void();
int mid=(l+r)>>1;
build(lson);build(rson);
pushup(o);
}
void update(int o,int l,int r,int ql,int qr,int c){
if(l>=ql && r<=qr) return setcov(o,c);
pushdown(o);
int mid=(l+r)>>1;
if(mid>=ql) update(lson,ql,qr,c);
if(mid<qr) update(rson,ql,qr,c);
pushup(o);
}
int query(int o,int l,int r,int ql,int qr){
if(l>=ql && r<=qr) return col[o];
pushdown(o);
int mid=(l+r)>>1;
if(mid<ql) return query(rson,ql,qr);
if(mid>=qr) return query(lson,ql,qr);
int x=query(lson,ql,qr),y=query(rson,ql,qr);
return x==y?x:-1;
}
void update(int u,int v,int c){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
update(1,1,n,dfn[top[u]],dfn[u],c);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
update(1,1,n,dfn[u],dfn[v],c);
}
int query(int u,int v){
int col=-2;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
int x=query(1,1,n,dfn[top[u]],dfn[u]);
if(col==-2) col=x;
else col=col==x?col:-1;
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
int x=query(1,1,n,dfn[u],dfn[v]);
if(col==-2) col=x;
return col==x?col:-1;
}
int main(){
n=read();m=read();
FOR(i,1,n-1){
int u=read(),v=read();
add(u,v);add(v,u);
}
dfs1(1,0);dfs2(1,1);
FOR(i,1,m){
int u=read(),v=read(),w=dep[u]+dep[v]-2*dep[lca(u,v)];
h[i]=(hhh){u,v,w};
}
sort(h+1,h+m+1);
build(1,1,n);
FOR(i,1,m){
int u=h[i].u,v=h[i].v;
if(query(u,v)==-1) return puts("No"),0;
update(u,v,++cc);
}
puts("Yes");
}
OJ
对于每条边,求出当询问的 \(r\) 是正无穷,\(l\) 最小是多少时,这条边会在最小生成森林上(设这个为 \(x_i\))。
这个可以排序后用 LCT 维护最大生成森林。
然后询问就变成了,对于边权在 \([l,r]\) 的边,把 \(x_i\le l\) 的边权求和。
可持久化线段树板子。
时间复杂度反正一个 log。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=111111,maxm=1111111,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
struct edge{
int u,v,w;
bool operator<(const edge &e)const{return w<e.w;}
}e[maxn];
int n,m,q,fa[maxn],ch[maxn][2],w[maxn],x[maxn],cnt,ls[maxm*20],rs[maxm*20],rt[maxn];
ll sum[maxm*20],lstans;
bool rev[maxn];
PII mn[maxn],val[maxn];
inline void pushup(int x){
mn[x]=min(min(mn[ch[x][0]],mn[ch[x][1]]),val[x]);
}
inline void setrev(int x){
rev[x]^=1;
swap(ch[x][0],ch[x][1]);
}
inline void pushdown(int x){
if(rev[x]){
if(ch[x][0]) setrev(ch[x][0]);
if(ch[x][1]) setrev(ch[x][1]);
rev[x]=0;
}
}
inline bool nroot(int x){
return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
}
inline void rotate(int x){
int y=fa[x],z=fa[y],t=ch[y][1]==x,tt=ch[z][1]==y,w=ch[x][t^1];
if(w) fa[w]=y;ch[y][t]=w;
fa[x]=z;if(nroot(y)) ch[z][tt]=x;
fa[y]=x;ch[x][t^1]=y;
pushup(y);pushup(x);
}
inline void pushall(int x){
if(nroot(x)) pushall(fa[x]);
pushdown(x);
}
inline void splay(int x){
pushall(x);
while(nroot(x)){
int y=fa[x],z=fa[y],t=ch[y][1]==x,tt=ch[z][1]==y;
if(nroot(y)) rotate(t^tt?x:y);
rotate(x);
}
}
void access(int x){
for(int y=0;x;x=fa[y=x]) splay(x),ch[x][1]=y,pushup(x);
}
void makeroot(int x){
access(x);splay(x);setrev(x);
}
int findroot(int x){
access(x);splay(x);
for(pushdown(x);ch[x][0];pushdown(x)) x=ch[x][0];
splay(x);
return x;
}
void split(int x,int y){
makeroot(x);access(y);splay(y);
}
void link(int x,int y){
makeroot(x);fa[x]=y;
}
void cut(int x,int y){
split(x,y);fa[x]=ch[y][0]=0;
}
void build(int &x,int l,int r){
x=++cnt;
if(l==r) return;
int mid=(l+r)>>1;
build(ls[x],l,mid);
build(rs[x],mid+1,r);
}
void update(int &x,int y,int l,int r,int p,int v){
x=++cnt,ls[x]=ls[y],rs[x]=rs[y],sum[x]=sum[y];
sum[x]+=v;
if(l==r) return;
int mid=(l+r)>>1;
if(mid>=p) update(ls[x],ls[y],l,mid,p,v);
else update(rs[x],rs[y],mid+1,r,p,v);
}
int query(int x,int l,int r,int ql,int qr){
if(!x) return 0;
if(l>=ql && r<=qr) return sum[x];
int mid=(l+r)>>1;
if(mid<ql) return query(rs[x],mid+1,r,ql,qr);
if(mid>=qr) return query(ls[x],l,mid,ql,qr);
return query(ls[x],l,mid,ql,qr)+query(rs[x],mid+1,r,ql,qr);
}
void clear(){
FOR(i,1,cnt) ls[i]=rs[i]=sum[i]=0;
FOR(i,1,m) rt[i]=x[i]=0;
cnt=0;
FOR(i,0,n+m) fa[i]=ch[i][0]=ch[i][1]=rev[i]=0,val[i]=mn[i]=MP(0,0);
lstans=0;
}
void solve(){
n=read();m=read();
FOR(i,1,m) e[i].u=read(),e[i].v=read(),e[i].w=read();
sort(e+1,e+m+1);
FOR(i,0,n) mn[i]=val[i]=MP(1e9,i);
FOR(i,n+1,n+m) mn[i]=val[i]=MP(w[i-n]=e[i-n].w,i);
FOR(i,1,m){
int u=e[i].u,v=e[i].v;
makeroot(u);
if(findroot(v)==u){
split(u,v);
PII p=mn[v];
int id=p.second-n,uu=e[id].u,vv=e[id].v;
x[i]=w[id]+1;
cut(uu,id+n);cut(vv,id+n);
}
link(u,i+n);link(v,i+n);
}
FOR(i,1,m) update(rt[i],rt[i-1],0,1e6+1,x[i],w[i]);
q=read();
while(q--){
int l=read()-lstans,r=read()-lstans;
int L=lower_bound(w+1,w+m+1,l)-w;
int R=lower_bound(w+1,w+m+1,r+1)-w-1;
if(L>R) printf("%lld\n",lstans=0);
else printf("%lld\n",lstans=query(rt[R],0,1e6+1,0,l)-query(rt[L-1],0,1e6+1,0,l));
}
clear();
}
int main(){
int T=read();
while(T--) solve();
}
IK
首先,如果有点没有入度或没有出度,显然无解。
考虑一条链(即除了最后一个点出度都是 \(1\),除了第一个点入度都是 \(1\),还要特判下 \(1\) 号点),它们显然可以看成一体,缩起来。
\(m\le n+20\),所以缩完之后点数不会太多(大概 \(20,21\) 这样)。然后直接开搜。
复杂度大概是出度之积乘个 \(m-n\) 之类的,不会超过 \(O(2^{m-n}(m-n)+n+m)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=111111,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,m,el,head[maxn],to[maxn],nxt[maxn],rd[maxn],cd[maxn],cnt,lst[maxn],ans[maxn],al,fa[maxn],son[maxn],tmp[maxn][2],tl,id[maxn];
vector<int> vec[maxn];
bool vis[maxn];
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
void dfs(int u){
vis[u]=true;cnt--;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==1 && !cnt){
ans[1]=1;
ans[al=2]=u;
while(ans[al]!=1) ans[al+1]=lst[ans[al]],al++;
ROF(i,al,2) FOR(j,0,(int)vec[ans[i]].size()-1) printf("%d ",vec[ans[i]][j]);
puts("1");
exit(0);
}
if(!vis[v]) lst[v]=u,dfs(v);
}
vis[u]=false;cnt++;
}
int main(){
freopen("king.in","r",stdin);
freopen("king.out","w",stdout);
n=read();m=read();
FOR(i,1,m){
int u=read(),v=read();
add(u,v);
cd[u]++;rd[v]++;
}
FOR(i,1,n) if(!rd[i] || !cd[i]) return puts("There is no route, Karl!"),0;
FOR(i,1,n) if(cd[i]==1 && rd[to[head[i]]]==1 && to[head[i]]!=1) fa[to[head[i]]]=i,son[i]=to[head[i]];
else for(int j=head[i];j;j=nxt[j]) tmp[++tl][0]=i,tmp[tl][1]=to[j];
FOR(i,1,n) if(son[i] && !fa[i]){
int now=i;
while(son[now]){
vec[i].PB(now);
id[now]=i;
now=son[now];
}
vec[i].PB(now);
id[now]=i;
}
else if(!id[i]) vec[i].PB(i),id[i]=i;
FOR(i,1,n) if(!fa[i]) cnt++;
MEM(head,0);MEM(to,0);MEM(nxt,0);el=0;
FOR(i,1,tl) add(id[tmp[i][0]],id[tmp[i][1]]);
dfs(1);
puts("There is no route, Karl!");
}
LK
注意到环长 \(m=7\) 是质数,也就是说对于每一个环,转到最大值的位置只可能是 \(1\) 个或 \(m\) 个。
如果所有位置都唯一,那么不妨选定一个方向为正方向,并求出每个环要在这个方向上转多少。问题变成给定序列 \(a\),每次选一个区间,可以任意在模 \(m\) 意义下加一个数,变成全 \(0\) 的最小次数。
显然差分,设 \(d_i=(a_i-a_{i-1})\bmod m\)(注意 \(d_{n+1}\) 也要考虑)。那么就是每次选一个 \(d_i\) 加 \(x\),另一个 \(d_j\) 减 \(x\)(都在模意义下)。
注意到对一个数又加又减肯定不优(可以一波操作抵消掉)。
假如我们枚举了哪些是一直加、哪些是一直减。注意要满足所有加的 \((m-d_i)\) 和减的 \(d_i\) 的和相等。而我们知道 \(\sum d_i\) 是多少(显然是 \(m\) 的倍数),所以相当于选择 \(\frac{\sum d_i}{m}\) 个数加,剩下的减。而且任意这么选择都是合法的。
注意到对于一个 \(x\) 个加,\(y\) 个减的子集,若加的 \((m-d_i)\) 和减的 \(d_i\) 的和相等,那么就可以至多 \(x+y-1\) 次操作完成。这启发我们将所有数分成尽量多个这样的组,每个组内操作。而且若有一个操作孤立的组不满足上面这个条件,显然这组不能清零。所以这是充要的。
所以答案就是 \(n+1-cnt\),其中 \(cnt\) 是最大组数。
其实上面那个条件就等价于所有数的和是 \(m\) 的倍数。
显然 \(0\) 每个单独一组,然后 \(1\) 和 \(6\) 组对子,\(2\) 和 \(5\),\(3\) 和 \(4\)。(组对子最优,是因为如果这个数不用来组对子,就必须要用一些和等于另一个数的来凑这组,显然不如一个数就完成这组优。)
(然后我就不会了,看题解去了。发现题解什么都没说。回想了一下我第一次氪 ZROI 的第一场,发现那题跟这题除了有一些多种转法的环,完全一样,甚至 \(m=7\) 都一样,但是当时太自闭了忘掉了。当时我连第一步都不会,但现在我已经做到这一步了,居然卡在只剩三种数这一步,我真的是个 sb。)
然后会发现 \(1\) 和 \(6\) 只剩一种,\(2\) 和 \(5\) 只剩一种,\(3\) 和 \(4\) 只剩一种。
那么就 \(f_{i,j,k}\) 表示最后那三种数现在分别还有 \(i,j,k\) 个(注意不一定满足和是 \(m\) 的倍数),能凑出多少组。
具体的转移是加一个数,然后判断此时的和是不是 \(m\) 的倍数,如果是就能凑出新的一组。
这一部分就可以 \(O(n^3)\) 配很小的常数。
接下来再考虑多种转法的环,就是要为每一个钦定一个最后转到的位置。发现直接忽略它们即可。因为忽略了它们再操作,也可以调整它们最后的位置使得它们恰好归位。而不忽略它们显然也不能让答案更小。
最后时间复杂度 \(O(n^3)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=555,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,m,a[maxn],cnt[7],ans,x,y,z,f[2][maxn][maxn],cur;
char s[10],t[10][10];
inline void chkmax(int &x,int y){if(y>x) x=y;}
inline bool chk(int a,int b,int c){return (x*a+y*b+z*c)%7==0;}
int main(){
n=read();
FOR(i,1,n){
scanf("%s",s);
bool flag=true;
FOR(j,1,6) if(s[j]!=s[0]){flag=false;break;}
if(flag) continue;
FOR(j,0,6) t[0][j]=s[j];
FOR(j,1,6){
t[j][0]=t[j-1][6];
FOR(k,1,6) t[j][k]=t[j-1][k-1];
}
m++;
FOR(j,1,6) if(strcmp(t[j],t[a[m]])>0) a[m]=j;
}
if(!m) return puts("0"),0;
FOR(i,1,m+1) cnt[(a[i]-a[i-1]+7)%7]++;
ans=m+1-cnt[0];
if(cnt[1]<cnt[6]) swap(cnt[1],cnt[6]),x=6;
else x=1;
ans-=cnt[6];cnt[1]-=cnt[6];
if(cnt[2]<cnt[5]) swap(cnt[2],cnt[5]),y=5;
else y=2;
ans-=cnt[5];cnt[2]-=cnt[5];
if(cnt[3]<cnt[4]) swap(cnt[3],cnt[4]),z=4;
else z=3;
ans-=cnt[4];cnt[3]-=cnt[4];
MEM(f,~0x3f);
f[0][0][0]=0;
FOR(i,0,cnt[1]){
FOR(j,0,cnt[2]) FOR(k,0,cnt[3]) f[cur^1][j][k]=-1e9;
FOR(j,0,cnt[2]) FOR(k,0,cnt[3]){
chkmax(f[cur^1][j][k],f[cur][j][k]+chk(i+1,j,k));
chkmax(f[cur][j+1][k],f[cur][j][k]+chk(i,j+1,k));
chkmax(f[cur][j][k+1],f[cur][j][k]+chk(i,j,k+1));
}
cur^=1;
}
ans-=f[cur^1][cnt[2]][cnt[3]];
printf("%d\n",ans);
}
RG
又是普及难度?
找出每个点在根的哪个儿子的子树中,最小边数显然是儿子个数。
至于被断掉的点数,就是对于同一子树里的求 LCA。这个就用 DFS 序的最大值和最小值两个点就够了。
时间复杂度 \(O(n+q\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=111111,mod=998244353;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,q,el,head[maxn],to[maxn],nxt[maxn],fa[maxn],dep[maxn],sz[maxn],siz[maxn],dfn[maxn],cnt,son[maxn],top[maxn],id[maxn],ans1,ans2;
char op[10];
struct cmp{
bool operator()(int x,int y){
return dfn[x]<dfn[y];
}
};
set<int,cmp> s[maxn];
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
void dfs1(int u){
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
dep[v]=dep[u]+1;
dfs1(v);
sz[u]+=sz[v];
siz[u]+=siz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
sz[u]++;
if(!siz[u]) siz[u]++;
}
void dfs2(int u,int topf){
top[u]=topf;
dfn[u]=++cnt;
id[cnt]=u;
if(son[u]) dfs2(son[u],topf);
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==son[u]) continue;
dfs2(v,v);
}
}
int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
int find(int u){
while(top[u]!=1){
if(fa[top[u]]==1) return top[u];
u=fa[top[u]];
}
return son[1];
}
int main(){
freopen("gangsters.in","r",stdin);
freopen("gangsters.out","w",stdout);
n=read();q=read();
FOR(i,2,n) add(fa[i]=read(),i);
dfs1(1);dfs2(1,1);
while(q--){
scanf("%s",op);
int u=read();
if(op[0]=='+'){
int v=find(u);
if(s[v].empty()) ans1++;
else ans2-=siz[lca(*s[v].begin(),*s[v].rbegin())];
s[v].insert(u);
ans2+=siz[lca(*s[v].begin(),*s[v].rbegin())];
ans2--;
}
else{
int v=find(u);
ans2-=siz[lca(*s[v].begin(),*s[v].rbegin())];
s[v].erase(u);
if(s[v].empty()) ans1--;
else ans2+=siz[lca(*s[v].begin(),*s[v].rbegin())];
ans2++;
}
printf("%d %d\n",ans1,ans2);
}
}