5.26 考试修改+总结

论写5K+的代码在只有样例的条件下都可以调对

由此可见,勇气才是成功的关键

先放题解吧

第一题上午写的暴力不小心忘记题目换根之后还会染色了

然后就挂成了5分QAQ

有很大的部分分是SDOI染色,还有一部分是旅行

但是考试犯懒没有写

很容易发现任何一种颜色在树上都是连续的一段

那么我们不妨这么定义,如果一条边两端颜色不相同,我们定义为虚边,会对子树每个答案产生+1的贡献

如果两端颜色相同,我们定义为实边,不会产生贡献

不难发现,这样定义后的实边和虚边的性质和LCT的定义是一样的

我们考虑使用LCT来维护,每次修改到根的路径就是一个Access

每次Access会使一些实边变成虚边,一些虚边变成实边

很容易在Access的过程中确定这些边,可以发现这些边的数量是等价于LCT复杂度的

也就是单次均摊O(logn),我们对于每次边的变化在对DFS序维护一颗线段树进行子树加减和子树求和就可以了

注意到这里LCT的儿子并不是原树中的儿子,所以我们要对于每个点维护他一直向左走到达的点L

这样修改的目标节点是LCT中的儿子的L,但是由于有换根操作,我们会有rev标记,所以我们还要额外维护一直向右走的点R

我们考虑换根操作,LCT显然可以直接换根

对于我们的线段树,我们可以使用类似于BZOJ 遥远的国度 的讨论方法,可以在不换根的情况下讨论出换根的信息

具体讨论方法就不在多说了,注意如果是菊花树,当根在当前点的子树内的时候,暴力找会挂掉

所以我们对于每个点开vector记录孩子,然后把孩子按DFS序排序,每次二分即可

总时间复杂度O(nlog^2n)

然后ftp挂掉了,没有数据的情况下我居然调了调就A了!撒花~~

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<vector>
using namespace std;

typedef long long LL;
const int maxn=100010;
int n,m,u,v,rt;
LL ans,sz;
char s[maxn];
int h[maxn],cnt=0;
struct edge{
	int to,next;
}G[maxn<<1];
int pos[maxn],ed[maxn],tot=0;
vector<int>V[maxn];
LL S[maxn<<2];
int Add[maxn<<2];
int siz[maxn];
bool cmp(const int &x,const int &y){
	return pos[x]<pos[y];
}
void add(int x,int y){
	++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt;
}
void push_down(int o,int L,int mid,int R){
	int cl=(o<<1),cr=(o<<1|1);
	int now=Add[o];Add[o]=0;
	S[cl]+=now*(mid-L+1);Add[cl]+=now;
	S[cr]+=now*(R-mid);Add[cr]+=now;
}
void UPD(int o,int L,int R,int x,int y,int v){
	if(L>=x&&R<=y){
		S[o]=S[o]+v*(R-L+1);
		Add[o]+=v;
		return;
	}
	int mid=(L+R)>>1;
	if(Add[o]!=0)push_down(o,L,mid,R);
	if(y<=mid)UPD(o<<1,L,mid,x,y,v);
	else if(x>mid)UPD(o<<1|1,mid+1,R,x,y,v);
	else {UPD(o<<1,L,mid,x,y,v);UPD(o<<1|1,mid+1,R,x,y,v);}
	S[o]=S[o<<1]+S[o<<1|1];
}
LL ask(int o,int L,int R,int x,int y){
	if(L>=x&&R<=y)return S[o];
	int mid=(L+R)>>1;
	if(Add[o]!=0)push_down(o,L,mid,R);
	if(y<=mid)return ask(o<<1,L,mid,x,y);
	else if(x>mid)return ask(o<<1|1,mid+1,R,x,y);
	else return ask(o<<1,L,mid,x,y)+ask(o<<1|1,mid+1,R,x,y);
}
int find_pos(int u,int v){
	int L=0,R=V[u].size()-1;
	while(L<R){
		int mid=L+((R-L+1)>>1);
		int now=V[u][mid];
		if(pos[v]>=pos[now])L=mid;
		else R=mid-1;
	}return V[u][L];
}
void Get_modify(int u,int val){
	if(u==rt)UPD(1,1,n,1,n,val);
	else if(pos[rt]<=ed[u]&&pos[rt]>=pos[u]){
		int v=find_pos(u,rt);
		UPD(1,1,n,1,n,val);
		UPD(1,1,n,pos[v],ed[v],-val);
	}else UPD(1,1,n,pos[u],ed[u],val);
}
LL Get_ans(int u){
	if(u==rt)return ask(1,1,n,1,n);
	else if(pos[rt]<=ed[u]&&pos[rt]>=pos[u]){
		int v=find_pos(u,rt);
		LL A=ask(1,1,n,1,n);
		LL B=ask(1,1,n,pos[v],ed[v]);
		return A-B;
	}else return ask(1,1,n,pos[u],ed[u]);
}
int Get_sz(int u){
	if(u==rt)return n;
	else if(pos[rt]<=ed[u]&&pos[rt]>=pos[u]){
		int v=find_pos(u,rt);
		return n-siz[v];
	}else return siz[u];
}
struct link_cut_tree{
	int fa[maxn],c[maxn][2];
	int rev[maxn],L[maxn],R[maxn];
	#define fa(u) fa[u]
	#define c(u,i) c[u][i]
	#define rev(i) rev[i]
	#define L(i) L[i]
	bool isroot(int u){return c(fa(u),0)!=u&&c(fa(u),1)!=u;}
	void pre(int p){if(!isroot(p))pre(fa(p));down(p);}
	void flip(int u){swap(c(u,0),c(u,1));swap(L[u],R[u]);rev(u)^=1;}
	void init(){for(int i=1;i<=n;++i)L[i]=i,R[i]=i;}
	void down(int u){
		if(rev(u)){
			if(c(u,0))flip(c(u,0));
			if(c(u,1))flip(c(u,1));
			rev(u)=0;
		}return;
	}
	void up(int u){
		if(c(u,0))L[u]=L[c(u,0)];
		else L[u]=u;
		if(c(u,1))R[u]=R[c(u,1)];
		else R[u]=u;
	}
	void rotate(int p,int x){
		int mark= p==c(x,1),y=c(p,mark^1),z=fa(x);
		if(c(z,0)==x)c(z,0)=p;
		if(c(z,1)==x)c(z,1)=p;
		if(y)fa(y)=x;
		fa(p)=z;c(p,mark^1)=x;fa(x)=p;c(x,mark)=y;
		up(x);
	}
	void Splay(int p){
		pre(p);
		while(!isroot(p)){
			int x=fa(p),y=fa(x);
			if(isroot(x))rotate(p,x);
			else if(p==c(x,0)^x==c(y,0))rotate(p,x),rotate(p,y);
			else rotate(x,y),rotate(p,x);
		}up(p);return;
	}
	void Access(int u){
		for(int v=0;u;u=fa(u)){
			Splay(u);
			fa(v)=u;
			if(c(u,1))Get_modify(L[c(u,1)],1);
			c(u,1)=v;
			if(v)Get_modify(L[v],-1);
			v=u;
		}return;
	}
	void make_root(int u){Access(u);Splay(u);flip(u);}
}LCT;

void read(int &num){
	num=0;char ch=getchar();
	while(ch<'!')ch=getchar();
	while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
void Get_DFS(int u,int f){
	pos[u]=++tot;siz[u]=1;
	LCT.fa[u]=f;
	for(int i=h[u];i;i=G[i].next){
		int v=G[i].to;
		if(v==f)continue;
		V[u].push_back(v);
		Get_DFS(v,u);
		siz[u]+=siz[v];
	}ed[u]=tot;
}

int main(){
	read(n);read(m);
	for(int i=1;i<n;++i){
		read(u);read(v);
		add(u,v);add(v,u);
	}Get_DFS(1,0);rt=1;
	LCT.init();
	for(int i=1;i<=n;++i)sort(V[i].begin(),V[i].end(),cmp);
	for(int i=1;i<=n;++i)UPD(1,1,n,pos[i],ed[i],1);
	for(int i=1;i<=m;++i){
		scanf("%s",s+1);
		read(u);
		if(s[3]=='Q'){
			ans=Get_ans(u);
			sz=Get_sz(u);
			printf("%.10lf\n",(double)(ans)/(double)(sz));
		}else if(s[3]=='L'){
			LCT.Access(u);
		}else{
			//rt=u;
			LCT.make_root(u);
			rt=u;
		}
	}return 0;
}

第二题很容易发现用组合数算出取哪k个,然后这k个数组成一个置换的方案是(k-1)!,剩余的数就是一个错排函数f

那么就是C(n,k)*(k-1)!*f(n-k)

但是我们发现对于同一个序列,因为它可能有多个k个数组成一个置换的情况,所以我们会算重

然后容斥一下就好了,容斥的式子也是很容易推导的

#include<cstdio> 
#include<cstring> 
#include<cstdlib> 
#include<iostream> 
#include<algorithm> 
using namespace std; 
  
typedef long long LL; 
const int maxn=500010; 
const int mod=1e9+7; 
int n; 
LL jc[maxn],inv[maxn]; 
LL f[maxn],g[maxn]; 
LL ans=0; 
  
LL pow_mod(LL v,int p){ 
    LL tmp=1; 
    while(p){ 
        if(p&1)tmp=tmp*v%mod; 
        v=v*v%mod;p>>=1; 
    }return tmp; 
} 
LL C(int n,int m){ 
    return jc[n]*inv[m]%mod*inv[n-m]%mod; 
} 
  
int main(){ 
    scanf("%d",&n); 
    jc[0]=1; 
    for(int i=1;i<=n;++i)jc[i]=jc[i-1]*i%mod; 
    inv[n]=pow_mod(jc[n],mod-2); 
    for(int i=n-1;i>=0;--i)inv[i]=inv[i+1]*(i+1)%mod; 
    f[0]=1;f[1]=0; 
    for(int i=2;i<=n;++i)f[i]=(f[i-1]+f[i-2])*(i-1)%mod; 
    g[0]=1;g[1]=0;g[2]=1; 
    for(int i=3;i<=n;++i)g[i]=g[i-1]*(i-1)%mod; 
    for(int k=2;k<=n;++k){ 
        int cnt=0; 
        LL tmp=1; 
        for(int j=k;j<=n;j+=k){ 
            cnt++; 
            tmp=tmp*C(n-j+k,k)%mod; 
            tmp=tmp*g[k]%mod; 
            if(cnt&1)ans=ans+tmp*inv[cnt]%mod*f[n-j]%mod; 
            else ans=ans-tmp*inv[cnt]%mod*f[n-j]%mod; 
            if(ans<0)ans+=mod; 
            if(ans>=mod)ans-=mod; 
        } 
    }printf("%lld\n",(ans%mod+mod)%mod); 
    return 0; 
}

第三题是个非常丝薄的题目

首先我们搞出这个序列,不难发现题目要求求极长最长上升子序列的个数

由于n<=1000,直接n^2暴力DP就可以了

考试的时候一直在想这个题目是可以O(n)做的啊,后来才发现读入复杂度都是O(n^2)

#include<cstdio> 
#include<cstring> 
#include<iostream> 
#include<algorithm> 
#include<cstdlib> 
using namespace std; 
  
const int maxn=1010; 
const int oo=0x7fffffff; 
const int mod=1e9+7; 
int n,m,u,v; 
int deg[maxn]; 
int a[maxn]; 
int Num[maxn]; 
int dp[maxn]; 
bool vis[maxn][maxn]; 
  
void read(int &num){ 
    num=0;char ch=getchar(); 
    while(ch<'!')ch=getchar(); 
    while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar(); 
} 
  
int main(){ 
    read(n);read(m); 
    for(int i=1;i<=m;++i){ 
        read(u);read(v); 
        if(u>v)swap(u,v); 
        deg[u]++; 
    } 
    for(int i=1;i<=n;++i)Num[i]=i; 
    for(int i=1;i<=n;++i){ 
        int now=deg[i]+1; 
        a[i]=Num[now]; 
        for(int j=now;j<=n;++j)Num[j]=Num[j+1]; 
    } 
    n++;a[0]=0;a[n]=n; 
    for(int i=0;i<=n;++i){ 
        int mn=oo; 
        for(int j=i+1;j<=n;++j){ 
            if(a[j]>a[i]){ 
                if(mn>a[j])vis[i][j]=true; 
                mn=min(mn,a[j]); 
            } 
        } 
    } 
    dp[0]=1; 
    for(int i=1;i<=n;++i){ 
        for(int j=0;j<i;++j){ 
            if(vis[j][i]){ 
                dp[i]+=dp[j]; 
                if(dp[i]>=mod)dp[i]-=mod; 
            } 
        } 
    }printf("%d\n",(dp[n]%mod+mod)%mod); 
    return 0; 
}

 

今天考试比较好的地方:

1、很快的发现第二题的容斥并成功的推出了式子

2、第三题的模型很容易就看了出来

然后就A掉了第二题和第三题

不太好的地方:

1、第一题的花式暴力分懒得去写(导致最后连最简单的暴力都挂掉了,关键是没有用心)

2、第一题想到了维护类似于实边和虚边的东西,但是发现单次可能挂成O(n)就没有往下想

实际上在分析会发现均摊是O(logn)的

需要做的题目:SDOI 旅行

话说天天都坑着一堆题目要做。。

posted @ 2016-05-26 15:54  _Vertical  阅读(197)  评论(0编辑  收藏  举报