IOI2020 集训队作业 Part 1
CF504E Misha and LCP on Tree
直接树剖,变成一堆重链,然后用 SA 求 LCP。
时间复杂度 \(O((n+q)\log n)\)。
看起来细节就很多,以后补。
CF505E Mr. Kitayuta vs. Bamboos
最大值最小,二分答案。
倒过来看,一开始有 \(n\) 条高度都为 \(x\) 的竹子。共 \(m\) 天,每天开始,第 \(i\) 条竹子会往下缩 \(a_i\)(smsd),然后你可以 \(k\) 次操作,把一条竹子拔高 \(p\)。然后要整个过程中竹子都没有缩到地底下(高度非负),最后第 \(i\) 条竹子的高度 \(\ge h_i\)(输入给出的那个)。
先看高度非负的限制。对每条竹子都记录它哪天会缩到地底下。用个堆,每次取出最早缩到地底下的拔出来。如果无论如何最早的都会缩下去,就是不行。
然后当所有竹子缩到地底下的天数 \(>m\) 时,就不用管非负的限制了,判断每条竹子需要再操作几次才能最后 \(\ge h_i\)。
然后我个蠢货还想再用一次堆来模拟第二个过程。
这样时间复杂度是 \(O((n+mk\log n)\log nv)\)。
注意到其实堆是没有用的,因为我们只在意 \(\le m\) 天就缩下去的竹子,可以开个 vector 之类的记录下每天缩下去的竹子有哪些。
时间复杂度 \(O((n+mk)\log nv)\)。
下面第一篇代码是堆,第二篇代码是 vector。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,int> PII;
const int maxn=100010;
#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,k,p,h[maxn],a[maxn],cnt[maxn];
priority_queue<PII,vector<PII>,greater<PII> > pq;
bool yet_another_check(ll x,int rem){
ll sum=0;
FOR(i,1,n) sum+=max(0ll,(h[i]+1ll*m*a[i]-x+p-1)/p-cnt[i]);
return sum<=rem;
}
bool check(ll x){
while(!pq.empty()) pq.pop();
FOR(i,1,n) cnt[i]=0,pq.push(MP(x/a[i]+1,i));
FOR(i,1,m){
if(pq.top().first<=i) return false;
FOR(j,1,k){
ll v=pq.top().first;
int id=pq.top().second;
pq.pop();
if(v>m) return yet_another_check(x,k-j+1+k*(m-i));
cnt[id]++;
pq.push(MP((x+1ll*cnt[id]*p)/a[id]+1,id));
}
}
return yet_another_check(x,0);
}
int main(){
n=read();m=read();k=read();p=read();
FOR(i,1,n) h[i]=read(),a[i]=read();
ll l=0,r=2e14;
while(l<r){
ll mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%lld\n",l);
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100010;
#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,k,p,h[maxn],a[maxn],cnt[maxn],at;
vector<int> v[maxn];
bool yet_another_check(ll x,int rem){
ll sum=0;
FOR(i,1,n) sum+=max(0ll,(h[i]+1ll*m*a[i]-x+p-1)/p-cnt[i]);
return sum<=rem;
}
bool check(ll x){
FOR(i,1,m) v[i].clear();
FOR(i,1,n){
cnt[i]=0;
ll d=x/a[i]+1;
if(d<=m) v[d].PB(i);
}
at=0;
FOR(i,1,m){
while(at<=m && v[at].empty()) at++;
if(at>m) return yet_another_check(x,k*(m-i+1));
if(at<=i) return false;
FOR(j,1,k){
int id=v[at].back();
v[at].pop_back();
while(at<=m && v[at].empty()) at++;
if(at>m) return yet_another_check(x,k-j+1+k*(m-i));
cnt[id]++;
ll d=(x+1ll*cnt[id]*p)/a[id]+1;
if(d<=m) v[d].PB(id);
}
}
return yet_another_check(x,0);
}
int main(){
n=read();m=read();k=read();p=read();
FOR(i,1,n) h[i]=read(),a[i]=read();
ll l=0,r=2e14;
while(l<r){
ll mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%lld\n",l);
}
CF506E Mr. Kitayuta's Gift
CF512D Fox And Travelling
CF516D Drazil and Morning Exercise
先用两次 DP 求出每个点的 \(f_i\)。
如果我们以 \(f_i\) 最小的点为根,那么这棵树满足小根堆性质。
考虑反证。若存在一个点 \(v\),父亲为 \(u\),满足 \(f_v<f_u\),那么:
- 若 \(u\) 的最长路不经过 \(v\),那么 \(v\) 是可以沿着 \(u\) 走的,也就是 \(f_v>f_u\),矛盾。
- 若 \(u\) 的最长路经过 \(v\),那么根是可以沿着 \(u\) 再到 \(v\) 这么走的,\(f_{rt}>f_u\),和一开始根的 \(f\) 最小矛盾。
那么我们对于每次询问双指针。每次加一个点就合并一堆集合,删一个点根据上面的结论不会影响到连通性。
时间复杂度 \(O(qn\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=200020,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],w[maxn],rt=1,p[maxn],fa[maxn],sz[maxn];
ll d[maxn][2],d2[maxn],D[maxn];
bool vis[maxn];
inline bool cmp(int x,int y){
return D[x]<D[y];
}
inline void add(int u,int v,int w_){
to[++el]=v;nxt[el]=head[u];head[u]=el;w[el]=w_;
}
int getfa(int x){
return x==fa[x]?x:fa[x]=getfa(fa[x]);
}
void unite(int x,int y){
x=getfa(x);y=getfa(y);
if(x==y) return;
fa[x]=y;
sz[y]+=sz[x];
}
void dfs(int u,int f){
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f) continue;
dfs(v,u);
if(d[v][0]+w[i]>d[u][0]) d[u][1]=d[u][0],d[u][0]=d[v][0]+w[i];
else d[u][1]=max(d[u][1],d[v][0]+w[i]);
}
}
void dfs2(int u,int f){
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f) continue;
d2[v]=max(d2[v],d2[u]+w[i]);
if(d[v][0]+w[i]==d[u][0]) d2[v]=max(d2[v],d[u][1]+w[i]);
else d2[v]=max(d2[v],d[u][0]+w[i]);
dfs2(v,u);
}
}
int main(){
n=read();
FOR(i,1,n-1){
int u=read(),v=read(),w=read();
add(u,v,w);add(v,u,w);
}
dfs(1,0);dfs2(1,0);
FOR(i,1,n) D[i]=max(d[i][0],d2[i]);
FOR(i,1,n) p[i]=i;
sort(p+1,p+n+1,cmp);
q=read();
while(q--){
ll x=read();
FOR(i,1,n) fa[i]=i,sz[i]=1,vis[i]=0;
int cur=n,ans=1;
ROF(i,n,1){
vis[p[i]]=true;
while(D[p[cur]]-D[p[i]]>x){
sz[getfa(p[cur])]--;
vis[p[cur]]=false;
cur--;
}
for(int j=head[p[i]];j;j=nxt[j]) if(vis[to[j]]){
unite(p[i],to[j]);
ans=max(ans,sz[getfa(to[j])]);
}
}
printf("%d\n",ans);
}
}
CF516E Drazil and His Happy Friends
CF521D Shop
少数我能自己想出来的集训队作业(
首先,最优策略一定是先赋值,再加,最后乘。
赋值,对于同一个 \(x\) 只需要关心最大的那个,这个可以转成加法。
加法,对于同一个 \(x\) 一定是选最大的那些(包括赋值转换成的加法)。排序后可以变成乘法。
乘法,直接选最大的几个。注意到加法转成的乘法中,对于同一个 \(x\) 的倍率一定递减,所以选最大的几个,对于同一个 \(x\) 选的也一定是个前缀,还是正确的。
排序的比较是个小细节。如果我们把倍率看成 \(\frac{a}{b}\),那么有 \(0\le a-b\le 10^6,1\le b\le 10^{11}\)。那么按 \(\frac{a}{b}-1=\frac{a-b}{b}\) 排序,交叉相乘就可以避免精度误差和爆 long long。
输出方案也要注意赋值、加、乘的顺序。
时间复杂度 \(O(n\log n)\)。(假如 \(n,m,k\) 同阶)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010;
#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 it{
int a;
ll b;
int tp,id;
bool operator<(const it &i)const{
if(a*i.b!=i.a*b) return a*i.b>i.a*b;
return id<i.id;
}
};
int k,n,m;
ll a[maxn];
it to[maxn];
vector<it> v[maxn],mul;
int main(){
k=read();n=read();m=read();
FOR(i,1,k) a[i]=read();
FOR(i,1,k) to[i].b=1;
FOR(i,1,n){
int t=read(),x=read(),b=read();
if(t==1) to[x]=min(to[x],(it){b,1,1,i});
if(t==2) v[x].PB((it){b,1,2,i});
if(t==3) mul.PB((it){b-1,1,3,i});
}
FOR(i,1,k) if(to[i].a>a[i]) v[i].PB((it){to[i].a-a[i],1,1,to[i].id});
FOR(i,1,k){
sort(v[i].begin(),v[i].end());
FOR(j,0,(int)v[i].size()-1){
mul.PB((it){v[i][j].a,a[i],v[i][j].tp,v[i][j].id});
a[i]+=v[i][j].a;
}
}
sort(mul.begin(),mul.end());
int l=min(m,(int)mul.size());
printf("%d\n",l);
FOR(i,0,l-1) if(mul[i].tp==1) printf("%d ",mul[i].id);
FOR(i,0,l-1) if(mul[i].tp==2) printf("%d ",mul[i].id);
FOR(i,0,l-1) if(mul[i].tp==3) printf("%d ",mul[i].id);
}
CF521E Cycling City
首先可以发现,当给定图是仙人掌时无解,因为一条边至多在一个环上。
判断仙人掌可以先搞出 DFS 树,对于非树边进行链上加。如果最后有边的权值 \(\ge 2\),那么就不是仙人掌,否则就是。
同时可以发现当给定图是仙人掌时一定有解。
难点在输出方案。 随便找一条权值 \(\ge 2\) 的边。这条边一定在某个方案中存在。 在非树边中随便找两条覆盖它的。 然后就分情况,发现可以归为一种模拟。
时间复杂度 \(O(n+m)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=444444;
#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))
template<typename T>
inline void read(T &x){
x=0;
char ch=getchar();bool f=false;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,m,el,head[maxn],to[maxn],nxt[maxn],dfn[maxn],sz[maxn],fa[maxn],dep[maxn],dfs_cnt;
int U[maxn],V[maxn],ncnt,cnt[maxn],at,au,av,bu,bv,ans[3][maxn],al[3],tmp[maxn],tl;
bool ntr[maxn];
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;
dfn[u]=++dfs_cnt;
sz[u]=1;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f) continue;
if(dfn[v]){
ntr[i]=true;
if(dfn[v]<dfn[u]) cnt[u]++,cnt[v]--,U[++ncnt]=u,V[ncnt]=v;
continue;
}
dfs1(v,u);
sz[u]+=sz[v];
}
}
void dfs2(int u,int f){
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f || ntr[i]) continue;
dfs2(v,u);
cnt[u]+=cnt[v];
}
}
int main(){
read(n);read(m);
FOR(i,1,m){
int u,v;
read(u);read(v);
add(u,v);add(v,u);
}
FOR(i,1,n) if(!dfn[i]) dfs1(i,0),dfs2(i,0);
FOR(i,1,n) if(cnt[i]>=2){at=i;break;}
if(!at) return puts("NO"),0;
puts("YES");
FOR(i,1,ncnt) if(dfn[U[i]]>=dfn[at] && dfn[U[i]]<=dfn[at]+sz[at]-1 && dfn[fa[at]]>=dfn[V[i]] && dfn[fa[at]]<=dfn[V[i]]+sz[V[i]]-1){
if(au){bu=U[i];bv=V[i];break;}
else au=U[i],av=V[i];
}
int tau=au,tbu=bu;
while(tau!=tbu){
if(dep[tau]<dep[tbu]) swap(tau,tbu);
tau=fa[tau];
}
int lwr=tau,upr=dep[av]<dep[bv]?bv:av;
for(int i=lwr;i!=upr;i=fa[i]) ans[0][++al[0]]=i;
ans[0][++al[0]]=upr;
for(int i=upr;i!=av;i=fa[i]) ans[1][++al[1]]=i;
ans[1][++al[1]]=av;
for(int i=au;i!=lwr;i=fa[i]) ans[1][++al[1]]=i;
ans[1][++al[1]]=lwr;
for(int i=1,j=al[1];i<j;i++,j--) swap(ans[1][i],ans[1][j]);
for(int i=upr;i!=bv;i=fa[i]) ans[2][++al[2]]=i;
ans[2][++al[2]]=bv;
for(int i=bu;i!=lwr;i=fa[i]) ans[2][++al[2]]=i;
ans[2][++al[2]]=lwr;
for(int i=1,j=al[2];i<j;i++,j--) swap(ans[2][i],ans[2][j]);
FOR(i,0,2){
printf("%d ",al[i]);
FOR(j,1,al[i]) printf("%d ",ans[i][j]);
puts("");
}
return 0;
}
CF526F Pudding Monsters
连续段等同于 \(r-l=\max-\min\)。
所以,从小到大枚举右端点 \(r\),线段树对每个 \(i\) 都维护 \(l=i\) 时 \((r-l)-(\max-\min)\) 的值。
这个数肯定是恒 \(\ge 0\) 的,所以当最小值是 \(0\) 时,答案就加上取到最小值的数的个数。
实际上就是同时维护两个单调栈,压栈和弹栈都是对一个区间进行区间加。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1111111;
#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 int read(){
int x=0,f=0;char ch=getchar();
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 node{
int mn,mncnt;
node operator+(const node &nd)const{
if(mn<nd.mn) return *this;
else if(mn>nd.mn) return nd;
else return (node){mn,mncnt+nd.mncnt};
}
}seg[maxn];
int n,a[maxn],stkmx[maxn],tpmx,stkmn[maxn],tpmn,tag[maxn];
ll ans;
inline void setadd(int o,int v){
tag[o]+=v;
seg[o].mn+=v;
}
inline void pushdown(int o){
if(tag[o]){
setadd(o<<1,tag[o]);
setadd(o<<1|1,tag[o]);
tag[o]=0;
}
}
void build(int o,int l,int r){
if(l==r) return void(seg[o]=(node){l,1});
int mid=(l+r)>>1;
build(lson);build(rson);
seg[o]=seg[o<<1]+seg[o<<1|1];
}
void update(int o,int l,int r,int ql,int qr,int v){
if(l>=ql && r<=qr) return setadd(o,v);
int mid=(l+r)>>1;
pushdown(o);
if(mid>=ql) update(lson,ql,qr,v);
if(mid<qr) update(rson,ql,qr,v);
seg[o]=seg[o<<1]+seg[o<<1|1];
}
int main(){
n=read();
FOR(i,1,n){
int x=read(),y=read();
a[x]=y;
}
build(1,1,n);
stkmx[tpmx=1]=stkmn[tpmn=1]=0;
FOR(i,1,n){
update(1,1,n,1,n,-1);
while(tpmx>1 && a[i]>a[stkmx[tpmx]]) update(1,1,n,stkmx[tpmx-1]+1,stkmx[tpmx],-a[stkmx[tpmx]]),tpmx--;
update(1,1,n,stkmx[tpmx]+1,i,a[i]);
stkmx[++tpmx]=i;
while(tpmn>1 && a[i]<a[stkmn[tpmn]]) update(1,1,n,stkmn[tpmn-1]+1,stkmn[tpmn],a[stkmn[tpmn]]),tpmn--;
update(1,1,n,stkmn[tpmn]+1,i,-a[i]);
stkmn[++tpmn]=i;
ans+=seg[1].mncnt;
}
printf("%lld\n",ans);
}
CF526G Spiders Evil Plan
CF527E Data Center Drama
CF536D Tavas in Kansas
CF538G Berserk Robot
CF538H Summer Dichotomy
CF547D Mike and Fish
每个横坐标看成一个点,纵坐标看成一个点。输入的点就把对应的横坐标纵坐标连边。
问题变成把边定向,使得每个点入度出度之差不超过 1。
度数为偶数的点,一定是入度出度相等。度数为奇数的点,一定是入度出度相差正好是 1。
建一个虚点,和每个奇点连边。这样奇点变成的偶点,如果能让入度出度相等,那么原来的奇点就满足要求。
奇点有偶数个,所以虚点的度数也是偶数。
对每个联通块跑欧拉回路即可。
时间复杂度,忽略离散化(或之类的东西)是 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=1222222;
#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,x[maxn],y[maxn],tmpx[maxn],xl,tmpy[maxn],yl,deg[maxn],el=1,head[maxn],to[maxn],nxt[maxn];
char s[maxn];
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
void dfs(int u){
for(int &i=head[u];i;i=nxt[i]){
int v=to[i];
if(!s[i>>1]){
s[i>>1]=v>xl?'r':'b';
dfs(v);
}
}
}
int main(){
n=read();
FOR(i,1,n){
tmpx[++xl]=x[i]=read();
tmpy[++yl]=y[i]=read();
}
sort(tmpx+1,tmpx+xl+1);xl=unique(tmpx+1,tmpx+xl+1)-tmpx-1;
sort(tmpy+1,tmpy+yl+1);yl=unique(tmpy+1,tmpy+yl+1)-tmpy-1;
FOR(i,1,n){
x[i]=lower_bound(tmpx+1,tmpx+xl+1,x[i])-tmpx;
y[i]=lower_bound(tmpy+1,tmpy+yl+1,y[i])-tmpy;
add(x[i],y[i]+xl);add(y[i]+xl,x[i]);
deg[x[i]]++;deg[y[i]+xl]++;
}
FOR(i,1,xl+yl) if(deg[i]&1) add(i,xl+yl+1),add(xl+yl+1,i);
FOR(i,1,xl+yl+1) dfs(i);
FOR(i,1,n) printf("%c",s[i]);
}
CF547E Mike and Friends
拆成两个前缀的询问,离线。
然后发现就是 AC 自动机的 fail 树上单点修改和子树求和。
时间复杂度 \(O((q+\sum|s_i|)\log\sum|s_i|)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=1000100,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 ques{
int r,k,id,w;
bool operator<(const ques &q)const{return r<q.r;}
}qu[maxn];
int n,Q,tot,ch[maxn][26],at[maxn],fail[maxn],fa[maxn],q[maxn],h,r,el,head[maxn],to[maxn],nxt[maxn],lft[maxn],rig[maxn],dfn;
ll ans[maxn],b[maxn];
char s[maxn];
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
inline void update(int p,ll v){
for(int i=p;i<=tot+1;i+=i&-i) b[i]+=v;
}
inline ll query(int p){
ll s=0;
for(int i=p;i;i-=i&-i) s+=b[i];
return s;
}
inline ll query(int l,int r){
return query(r)-query(l-1);
}
void insert(char *s,int id){
int l=strlen(s+1),now=0;
FOR(i,1,l){
int p=s[i]-'a';
if(!ch[now][p]) ch[now][p]=++tot,fa[tot]=now;
now=ch[now][p];
}
at[id]=now;
}
void build(){
h=1;r=0;
FOR(i,0,25) if(ch[0][i]) q[++r]=ch[0][i];
while(h<=r){
int u=q[h++];
FOR(i,0,25) if(ch[u][i]){
fail[ch[u][i]]=ch[fail[u]][i];
q[++r]=ch[u][i];
}
else ch[u][i]=ch[fail[u]][i];
}
}
void dfs(int u){
lft[u]=++dfn;
for(int i=head[u];i;i=nxt[i]) dfs(to[i]);
rig[u]=dfn;
}
int main(){
n=read();Q=read();
FOR(i,1,n){
scanf("%s",s+1);
insert(s,i);
}
build();
FOR(i,1,tot) add(fail[i],i);
dfs(0);
FOR(i,1,Q){
int l=read(),r=read(),k=read();
qu[2*i-1]=(ques){l-1,k,i,-1};
qu[2*i]=(ques){r,k,i,1};
}
sort(qu+1,qu+2*Q+1);
int cur=1;
FOR(i,1,2*Q){
while(cur<=qu[i].r){
for(int u=at[cur];u;u=fa[u]) update(lft[u],1);
cur++;
}
ans[qu[i].id]+=qu[i].w*query(lft[at[qu[i].k]],rig[at[qu[i].k]]);
}
FOR(i,1,Q) printf("%lld\n",ans[i]);
}
CF549E Sasha Circle
CF553E Kyoya and Train
CF555E Case of Computer Network
少数自己能想出来的集训队作业(
然后我又没注意图可以不连通蛤蛤蛤(
联通块独立。下面假设图联通。
对于一个边双,显然可以定向成里面的点两两可互达。缩边双后变成棵树。
然后一个限制就是固定一条路径上的方向。
随便选个根,大概记录一下一条边是深度小的连向深度大的,还是相反,判断有没有矛盾就行了。
时间复杂度 \(O(n+m+q\log n)\)。
本来以为是个小清新题,结果写了 4 个 dfs(虽然也不难写
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=400040;
#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,q,U[maxn],V[maxn],el=1,head[maxn],to[maxn],nxt[maxn];
int id[maxn],dfn[maxn],low[maxn],cnt,tot,stk[maxn],tp;
int fa[maxn],dep[maxn],sz[maxn],son[maxn],top[maxn],s1[maxn],s2[maxn];
map<PII,bool> vis;
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
void dfs(int u,int f){
stk[++tp]=u;
dfn[u]=low[u]=++cnt;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(i==(f^1)) continue;
if(dfn[v]) low[u]=min(low[u],low[v]);
else dfs(v,i),low[u]=min(low[u],low[v]);
}
if(dfn[u]==low[u]){
tot++;
do{
id[stk[tp]]=tot;
}while(stk[tp--]!=u);
}
}
void dfs2(int u,int f){
dfn[u]=1;
dep[u]=dep[fa[u]=f]+1;
sz[u]=1;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f) continue;
dfs2(v,u);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs3(int u,int topf){
top[u]=topf;
if(son[u]) dfs3(son[u],topf);
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==fa[u] || v==son[u]) continue;
dfs3(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;
}
void dfs4(int u){
dfn[u]=1;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==fa[u]) continue;
dfs4(v);
s1[u]+=s1[v];
s2[u]+=s2[v];
}
}
int main(){
n=read();m=read();q=read();
FOR(i,1,m){
U[i]=read();V[i]=read();
add(U[i],V[i]);add(V[i],U[i]);
}
FOR(i,1,n) if(!dfn[i]) dfs(i,0);
MEM(head,0);MEM(to,0);MEM(nxt,0);
el=0;
FOR(i,1,m){
int u=id[U[i]],v=id[V[i]];
if(u!=v && !vis[MP(u,v)]){
vis[MP(u,v)]=vis[MP(v,u)]=true;
add(u,v);add(v,u);
}
}
MEM(dfn,0);
FOR(i,1,cnt) if(!dfn[i]) dfs2(i,0),dfs3(i,i);
while(q--){
int u=id[read()],v=id[read()];
int l=lca(u,v);
s1[u]++;s1[l]--;
s2[v]++;s2[l]--;
}
MEM(dfn,0);
FOR(i,1,cnt) if(!dfn[i]) dfs4(i);
FOR(i,1,cnt) if(s1[i] && s2[i]) return puts("No"),0;
puts("Yes");
}
CF559E Gerald and Path
CF566C Logistical Questions
CF566E Restoring Map
CF568C New Language
CF568E Longest Increasing Subsequence
CF571D Campus
CF571E Geometric Progressions
CF573E Bear and Bowling
CF575A Fibonotci
CF575E Spectator Riots
CF575I Robots protection
CF576D Flights for Regular Customers
CF576E Painting Edges
CF578E Walking!
CF578F Mirror Box
CF582D Number of Binominal Coefficients
应该也是自己想出来的一题吧(
语言和题解很像是因为我语文太差,借鉴了一下,信我啊(
设 \(f(x)\) 为 \(x\) 分解质因数后 \(p\) 的出现次数。
要求 \(f(\binom{n}{k})=f(n!)-f(k!)-f((n-k)!)\ge\alpha\)。
\(f(n!)\) 其实就是:把 \(n\) 写成 \(p\) 进制,每个前缀(不包括自己)的和。
考虑 \(k\) 加上 \(n-k\) 的过程,注意到每进位一次,这一位会减少 \(p\),前一位会加上 \(1\)。经过冷静思考,\(f\) 就会增加 \(1\)。
所以 \(k\) 加上 \(n-k\) 进了至少 \(\alpha\) 次位就可以了。
其实这一部分当时我是半推半猜,但是看了一波题解说是对的,自己证了一下,就当我是自己想出来的吧(
接下来就很蠢了。直接数位 DP,\(f_{i,j,0/1,0/1}\) 表示两个数都填了前 \(i\) 位,已经进位了 \(j\) 次,需不需要来自后面的进位(如果有肯定是进了 \(1\)),两个数的和有没有顶到上界的方案数。
转移不要枚举两个位分别是啥,因为 \(p\) 太大了。发现情况也就那么几种:顶到上界,进位,不进位之类的,而且每种填的方案数都很好算。
注意到 \(\alpha\le 10^9\) 是吓人的,因为进位次数 \(\le\log_pA\)。
时间复杂度看成 \(O(\log^2A)\) 好了。
代码等会补。(看起来很恶心的样子……)
CF582E Boolean Function
CF585E Present for Vitalik the Philatelist
\(f_i\) 为 \(\gcd=i\) 的集合个数。
\(g_i\) 为 \(i|\gcd\) 的集合个数。
\(c_i\) 为 \(i\) 的个数。
\(cnt_i\) 为 \(i\) 的倍数的个数。
为了方便,硬点 \(f_1=0\),原来的 \(f_1\) 是:
对 \(g\) 的影响,也即 \(g_1\) 减去原来的 \(f_1\)。
答案为:
直接做,用上“狄利克雷后缀和”的 trick,时间复杂度 \(O(n+v\log\log v)\)。
我也不清楚枚举倍数能不能过(
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=10001000,mod=1000000007;
#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,v,c[maxn],pw[maxn],pr[maxn/10],pl,mu[maxn],g[maxn],f1,ans;
bool vis[maxn];
void init(int upr){
mu[1]=1;
FOR(i,2,upr){
if(!vis[i]) mu[pr[++pl]=i]=-1;
FOR(j,1,pl){
int k=i*pr[j];
if(k>upr) break;
vis[k]=true;
if(i%pr[j]) mu[i*pr[j]]=-mu[i];
else break;
}
}
FOR(i,1,upr) if(mu[i]==-1) mu[i]=mod-1;
pw[0]=1;
FOR(i,1,upr) pw[i]=(pw[i-1]+pw[i-1])%mod;
}
void is_this_fmt(int *A){
FOR(i,1,pl) ROF(j,v/pr[i],1) A[j]=(A[j]+A[j*pr[i]])%mod;
}
int main(){
n=read();
while(n--){
int x=read();
c[x]++;
v=max(v,x);
}
init(v);
is_this_fmt(c);
FOR(i,1,v) g[i]=(pw[c[i]]-1+mod)%mod;
FOR(i,1,v) f1=(f1+1ll*mu[i]*g[i])%mod;
g[1]=(g[1]-f1+mod)%mod;
FOR(i,1,v) ans=(ans+1ll*mu[i]*c[i]%mod*g[i])%mod;
printf("%d\n",ans);
}