51nod一日游

忽然意识到昨天交了十几发的CE是因为没选择语言qwq
本来准备按着题目分类刷一遍的,看来是刷不完了

1618树或非树

树链剖分

思路还是挺简单的吧?
把环拎出来单独处理(n条边的联通图存在且仅存在一个环),那么若不考虑环
图中的连通图个数=总点数-"开"的边数
若这条环联通了,那么实际上有一条边并没有是减少联通块个数,但答案却已经减去了,所以ans++
边的开合用树链剖分维护即可

#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
inline char nc(){
	static char buf[100000],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
	char c=nc();x=0;
	for(;!isdigit(c);c=nc());
	for(;isdigit(c);c=nc())x=(x<<1)+(x<<3)+c-'0';
}
const int N=300002;
bool vis[N];
int n,m,head[N],nex[N<<1],to[N<<1],cnt,q[N],tot,c[N],c1,inc[N],bl[N];
struct data{int s,tag;}t[N<<3];
inline void addedge(int u,int v){
	nex[++cnt]=head[u],head[u]=cnt,to[cnt]=v;
}
inline void getcyc(int x,int fa){
	vis[x]=1,q[++tot]=x;
	for(int i=head[x];i;i=nex[i])
		if(!vis[to[i]])getcyc(to[i],x);
		else if(to[i]!=fa){
			if(c1)return;
			for(int y=0;y!=to[i];)y=q[tot--],c[++c1]=y,inc[y]=c1;
		}tot--;
}
int siz[N],dep[N],fa[N],son[N],top[N],pos[N],sz;
inline void dfs1(int x){
	siz[x]=1,dep[x]=dep[fa[x]]+1;
	for(int i=head[x];i;i=nex[i]){
		if(to[i]==fa[x]||inc[to[i]])continue;
		fa[to[i]]=x,bl[to[i]]=bl[x],dfs1(to[i]),siz[x]+=siz[to[i]],son[x]=siz[to[i]]>siz[son[x]]?to[i]:son[x];
	}
}
inline void dfs2(int x,int topf){
	pos[x]=++sz,top[x]=topf;
	if(!son[x])return;
	dfs2(son[x],topf);
	for(int i=head[x];i;i=nex[i])if(to[i]!=fa[x]&&!inc[to[i]]&&to[i]!=son[x])dfs2(to[i],to[i]);
}
inline void pushdown(int x,int l,int r){
	if(l==r||!t[x].tag)return;
	t[x].tag=0;int mid=l+r>>1;
	t[x<<1].tag^=1,t[x<<1|1].tag^=1;
	t[x<<1].s=mid-l+1-t[x<<1].s,t[x<<1|1].s=r-mid-t[x<<1|1].s;
}
inline void updata(int x,int l,int r,int L,int R){
	if(L>R)return;
	if(l==L&&r==R){
		t[x].tag^=1,t[x].s=r-l+1-t[x].s;return;
	}
	pushdown(x,l,r);int mid=l+r>>1;
	updata(x<<1,l,mid,L,min(R,mid)),updata(x<<1|1,mid+1,r,max(L,mid+1),R);
	t[x].s=t[x<<1].s+t[x<<1|1].s;
}
inline int query(int x,int l,int r,int L,int R){
	if(L>R)return 0;
	if(l==L&&r==R)return t[x].s;
	pushdown(x,l,r);int mid=l+r>>1;
	return query(x<<1,l,mid,L,min(R,mid))+query(x<<1|1,mid+1,r,max(L,mid+1),R);
}
inline void upd(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		updata(1,1,n+c1,pos[top[x]],pos[x]),x=fa[top[x]];
	}
	if(dep[x]<dep[y])swap(x,y);
	updata(1,1,n+c1,pos[y]+1,pos[x]);
}
int main(){
	read(n),read(m);int x,y,px,py;
	for(int u,v,i=1;i<=n;i++)read(u),read(v),addedge(u,v),addedge(v,u);
	getcyc(1,0);
	for(int i=1;i<=c1;i++)bl[c[i]]=c[i],dfs1(c[i]),dfs2(c[i],c[i]);
	while(m--){
		read(x),read(y);
		if(bl[x]==bl[y])upd(x,y);
		else{
			upd(x,bl[x]),upd(y,bl[y]),x=bl[x],y=bl[y],px=inc[x],py=inc[y];
			if(px<py){
				if(py-px<c1-py+px||py-px==c1-py+px&&c[px+1]<c[px==1?c1:px-1])updata(1,1,n+c1,px+1+n,py+n);
				else updata(1,1,n+c1,1+n,px+n),updata(1,1,n+c1,py+1+n,c1+n);
			}else{
				if(px-py<c1-px+py||px-py==c1-px+py&&c[px-1]<c[px%c1+1])updata(1,1,n+c1,py+1+n,px+n);
				else updata(1,1,n+c1,1+n,py+n),updata(1,1,n+c1,px+1+n,c1+n);
			}
		}
		int ans=n-t[1].s;
		if(query(1,1,n+c1,n+1,n+c1)==c1)ans++;
		printf("%d\n",ans);
	}
	return 0;
}

1742开心的小Q

莫比乌斯

果然数学差得已经不成样了,稍微复习了下
做不来——
留坑

1636教育改革

DP

比较愉快了

1789跑得比谁都快

斜率优化

很容易发现单调性,由此得到斜率优化;
在树上的话是多条链同时进行的,刚开始想用可持久化数组,可是不是特别好
想想看,记个前驱,后缀就能维护head,tail了,且一个数只会最多进队一次,O(n)的时间复杂度
然后——
1e18会爆炸啊,不想写高精度
那就改代码了...rp++

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
struct data{int pre,now,sub;}Z[N];
int nex[N],to[N],head[N],cnt;
int num,L,R,MX;
ll fa,n,p,C[N],ans,f[N],co[N];
void read(ll &x){
	char c=getchar();x=0;
	for(;!isdigit(c);c=getchar());
	for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
}
inline ll calc(int x,int i){return f[x]+C[i-x]+co[x];}
void ins(int u,int v){nex[++cnt]=head[u],head[u]=cnt,to[cnt]=v;}
int divi(int L,int R){
    int l=L+1,r=n;
    while(l<=r){
        int mid=l+r>>1;
        if(f[L]+C[mid-L]+co[L]>=f[R]+C[mid-R]+co[R])r=mid-1;else l=mid+1;
    }
    return r;
}
void dfs(int x){
    int l=L,r=R;
    for(;Z[L].sub&&calc(Z[L].now,x)>=calc(Z[Z[L].sub].now,x);L=Z[L].sub);
    f[x]=(x-Z[L].now<MX)?calc(Z[L].now,x):1e18;
    ans=(!head[x]&&ans>f[x])?f[x]:ans;
    for(;Z[R].pre&&divi(Z[Z[R].pre].now,Z[R].now)>=divi(Z[R].now,x);R=Z[R].pre);
    int last=Z[R].sub,nowR=R;
    Z[++num]=(data){R,x,0},Z[R].sub=num,R=num;
    for(int i=head[x];i;i=nex[i])dfs(to[i]);
    Z[nowR].sub=last,L=l,R=r;
}
int main(){
    read(n),read(p);
	for(int i=1;i<=n;i++){
        C[i]=1,MX=i;
        for(int j=1;j<=p;j++){
            C[i]=1ll*C[i]*i;
            if(C[i]>1e18)break;
        }
        if(C[i]>1e18)break;
    }
    MX=(MX==n&&C[n]<=1e18)?n+1:MX;
    ans=1e18;
    for(int i=1;i<=n;i++)read(co[i]),read(fa),ins(fa,i);
    L=R=num=1;Z[1]=(data){0,1,0};
    for(int i=head[1];i;i=nex[i])dfs(to[i]);
    return printf("%lld\n",ans),0;
}

1623完美消除

数位DP

维护一个单调队列(0-9)来维护消除次数

#include<cstdio>
#include<cstring>
typedef long long ll;
ll L,R,f[20][1<<10][20];
int k,a[20],t[11];
bool vis[20][1<<10][20];
inline int cal(int p,int i){return p=p&(t[i+1]-1);}
inline ll dfs(int pos,int s,int tt,bool lim){
	if(pos<0)return tt==k;
	if(!lim&&f[pos][s][tt]!=-1)return f[pos][s][tt];
	int mx=lim?a[pos]:9;ll res=0;
	for(int i=0;i<=mx;i++){
		if(s&t[i]||i==0)res+=dfs(pos-1,cal(s,i),tt,lim&&i==a[pos]);
		else res+=dfs(pos-1,cal(s|t[i],i),tt+1,lim&&i==a[pos]);
	}
	if(!lim)f[pos][s][tt]=res;
	return res;
}
inline ll solve(ll x){
	memset(f,-1,sizeof f);
	int pos=0;for(;x;a[pos++]=x%10,x/=10);
	return dfs(pos-1,0,0,1);
}
int main(){
	t[0]=1;for(int i=1;i<=10;i++)t[i]=t[i-1]<<1;
	scanf("%lld%lld%d",&L,&R,&k);
	return printf("%lld\n",solve(R)-solve(L-1)),0;
}

1486大大走格子

容斥,组合数

以前最年长的学长讲过
是个好题
用了组合数&容斥
如果每次只能向下或向右走
那么从(x1,y1)走到(x2,y2) [x1<=x2,y1<=y2]的方案数是C(x2-x1+y2-y1,x2-x1)
总方案数=C(h+w-2,h-1)-经过不能经过的位置的方案数
关键是怎样计算经过不能经过的位置的方案
现将点按x,y坐标排序
f[i]表示从左上角走过来经过的第一个不能经过位置是第i个点的方案数
则f[i]=C(a[i].x+a[i].y-2,a[i].x-1)-sigma(f[j]*C(a[i].x+a[i].y-a[j].x-a[j].y,a[i].x-a[j].x))[j<i&&a[j].x<=a[i].x&&a[j].y<=a[i].y]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2002,M=100002,mo=1e9+7;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
    char c=nc();x=0;
    for(;!isdigit(c);c=nc());
    for(;isdigit(c);c=nc())x=(x<<1)+(x<<3)+c-'0';
}
struct data{
    int x,y;
    bool operator<(const data &b)const{return x==b.x?y<b.y:x<b.x;}
}a[N];
int w,h,n;
ll ans,c[M<<1],f[N],inv[M<<1],cinv[M<<1];
ll C(int x,int y){return 1ll*c[x]*cinv[y]%mo*cinv[x-y]%mo;}
int main(){
	read(h),read(w),read(n);
	c[0]=inv[0]=c[1]=inv[1]=cinv[0]=cinv[1]=1;for(int i=2;i<=w+h;++i)c[i]=1ll*c[i-1]*i%mo,inv[i]=1ll*(mo-(mo/i))*inv[mo%i]%mo,cinv[i]=1ll*cinv[i-1]*inv[i]%mo;
    for(int i=1;i<=n;++i)read(a[i].x),read(a[i].y);
    sort(a+1,a+1+n); 
    for(int i=1;i<=n;++i){
        f[i]=C(a[i].x+a[i].y-2,a[i].x-1);
        for(int j=1;j<i;j++)if(a[i].y>=a[j].y)f[i]=(f[i]-f[j]*C(a[i].x-a[j].x+a[i].y-a[j].y,a[i].x-a[j].x)%mo)%mo;
        ans=(ans+f[i]*C(h-a[i].x+w-a[i].y,h-a[i].x)%mo)%mo;
    }
    printf("%lld\n",((C(h+w-2,h-1)-ans)%mo+mo)%mo);
    return 0;
}

1553周期串查询

hash和线段树

如果[l,r]周期为d
则[l,r-d]与[l+d,r]的hash值相同

1672区间交

先按照左端点排序,再用个树状数组,二分查找最右端的被覆盖至少k 次的点

1766树上的最远点对

线段树

一点都不会求求直径,也算填坑

我们知道,树上最远的距离是树的直径。
然后,直径可以由两个点集中的直径的总共四个端点两两配对得到。
于是我们就可以用线段树来维护这个东西。
注意求距离要用欧拉序列,不能用倍增,否则会爆炸性超时。
-alan_cty


简单证明一下。
我们看作是 x 所在的连通块通过边(x, y)连向 y 所在的连通块。
若新直径不经过(x, y),则就是原来的两条直径取 max。
若新直径经过(x, y),就要考虑 x 延伸到哪儿、y 延伸到哪儿了。由直径的定义可知,x 能走到的最远点之一是 x 所在连通块直径的端点,y 同理。因此这时新直径的两个端点都是旧直径的端点。
(这个证明也适用于增量法求树的直径,即我给一棵树加一个新点,那么新直径必有一端点是旧直径的端点)
(注意只能是树,普通图没有这些性质)
-rzO_KQP_Orz

#include<bits/stdc++.h>
using namespace std;
const int N=100002;
struct note{int a,b;}tr[N*5];
int n,m,k,tot,d[N],dfn[N<<1],fir[N],sum[N],f[N<<1][18];
int to[N<<1],nex[N<<1],w[N<<1],head[N],mi[18],cnt;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
    char c=nc();x=0;
    for(;!isdigit(c);c=nc());
    for(;isdigit(c);c=nc())x=(x<<1)+(x<<3)+c-'0';
}
void add(int u,int v,int wi){
    nex[++cnt]=head[u],head[u]=cnt,to[cnt]=v,w[cnt]=wi;
}
void dfs(int x,int y){
    d[x]=d[y]+1,dfn[++tot]=x,fir[x]=tot;
    for(int i=head[x];i;i=nex[i])if(to[i]!=y)sum[to[i]]=sum[x]+w[i],dfs(to[i],x),dfn[++tot]=x;
}
int lca(int x,int y){
    x=fir[x],y=fir[y];
    if(x>y)swap(x,y);
    int z=log2(y-x+1);
    if(d[dfn[f[x][z]]]<d[dfn[f[y-mi[z]+1][z]]])return dfn[f[x][z]];else return dfn[f[y-mi[z]+1][z]];
}
inline int len(int x,int y){
    int z=lca(x,y);return sum[x]+sum[y]-2*sum[z];
}
note merge(note y,note z,int bz){
    note x;int mx=0,l;
    if(!bz){
        l=len(y.a,y.b);if(l>mx)mx=l,x.a=y.a,x.b=y.b;
        l=len(z.a,z.b);if(l>mx)mx=l,x.a=z.a,x.b=z.b;
    }
    l=len(y.a,z.a);if(l>mx)mx=l,x.a=y.a,x.b=z.a;
    l=len(y.a,z.b);if(l>mx)mx=l,x.a=y.a,x.b=z.b;
    l=len(y.b,z.a);if(l>mx)mx=l,x.a=y.b,x.b=z.a;
    l=len(y.b,z.b);if(l>mx)mx=l,x.a=y.b,x.b=z.b;
    return x;
}
void build(int v,int l,int r){
    if(l==r){tr[v].a=tr[v].b=l;return;}
    int mid=(l+r)/2;
    build(v<<1,l,mid),build(v<<1|1,mid+1,r);
    tr[v]=merge(tr[v<<1],tr[v<<1|1],0);
}
note find(int v,int l,int r,int x,int y){
    if(l==x&&r==y)return tr[v];
    int mid=l+r>>1;
    if(y<=mid)return find(v<<1,l,mid,x,y);
    else if(x>mid)return find(v<<1|1,mid+1,r,x,y);
    else return merge(find(v<<1,l,mid,x,mid),find(v<<1|1,mid+1,r,mid+1,y),0);
}
int main(){
	freopen("t.in","r",stdin);
    read(n);
    for(int u,v,wi,i=1;i<n;i++)read(u),read(v),read(wi),add(u,v,wi),add(v,u,wi);
	dfs(1,0),mi[0]=1;for(int i=1;i<18;i++)mi[i]=mi[i-1]<<1;
	for(int i=1;i<=tot;i++)f[i][0]=i;
    for(int j=1;j<=log2(tot);j++)
        for(int i=1;i<=tot-mi[j]+1;i++)
            if(d[dfn[f[i][j-1]]]<d[dfn[f[i+mi[j-1]][j-1]]])f[i][j]=f[i][j-1];else f[i][j]=f[i+mi[j-1]][j-1];
    build(1,1,n);
    int x,y,z,k;
    for(read(m);m;m--){
        read(x),read(y),read(z),read(k);
        note tmp=merge(find(1,1,n,x,y),find(1,1,n,z,k),1);
        printf("%d\n",len(tmp.a,tmp.b));
    }
    return 0;
}
posted @ 2018-11-08 16:21  lnyzo  阅读(161)  评论(0编辑  收藏  举报