CCPC2018 湖南全國邀請賽補題(DP/並查集/最長上升子序列)(H待補)

D

比賽時的思路是計數,沒有想到DP,現在一想其實DP的挺明顯的。

這個思路是網上一個我看的比較明白的思路。

題解:

1. 直接把整個環看成相同數目的黑白段的連接(必定是相同數目)。

2. 用DP計算將i個數分解成j段的所有分發的乘積和,dp[i][j]=\sum_{k=1}^{i-j+1}dp[i-k][j-1]*k,dp的時候用前綴和和滾動數組優化。

3.拼接的時候ans=\sum_{i=1}^{min(n,m)}dp[n][i]*dp[m][i]*(n+m)*inv[i]後取模。

#include<bits/stdc++.h>
#define MOD 1000000007
#define N 5005
#define ll long long
using namespace std;
ll qpow(ll x,ll t){ return t==0?1LL:qpow((x*x)%MOD,t>>1)*(t%2?x:1)%MOD; }
ll inv[N];
ll dp[N][N];
ll s1[2][N];
ll s2[2][N];
void init(){
	int n=5000;
	for (int i=1;i<=n;i++) inv[i]=qpow(i,MOD-2);
	for (int i=1;i<=n;i++){ //geshu
		dp[i][1]=i;
		s1[i%2][1]=(s1[1-i%2][1]+dp[i][1])%MOD;
		s2[i%2][1]=(s2[1-i%2][1]+dp[i][1]*(n-i+1) )%MOD;
		for (int j=2;j<=i;j++){
			dp[i][j]=((s2[1-i%2][j-1]-s1[1-i%2][j-1]*(n-i+1))%MOD+MOD)%MOD;
			s1[i%2][j]=(s1[1-i%2][j]+dp[i][j]);
			s2[i%2][j]=(s2[1-i%2][j]+dp[i][j]*(n-i+1))%MOD;
		}
	}
}
int main(){
	init(); 
	ll n,m;
	while (~scanf("%lld%lld",&n,&m)){
		ll ans=0;
		if (n>m)  swap(n,m);
		for (int i=1;i<=n;i++){
			ans=(ans+dp[n][i]*dp[m][i]%MOD*(n+m)%MOD*inv[i]%MOD)%MOD;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

E

比賽的時候想用樹鏈剖分,寫了快兩百行代碼沒debug出來比賽結束,賽後發現直接用並查集維護就可以了。

比賽時已經發現的規律:每個邊都只會處理一次,因此直接用異或的性質來處理z的改變。

直接每次沿路徑向上即可,沿路合併,修改z值。

debug很久的原因是:加邊順序錯了……暈

#include<bits/stdc++.h>
#define N 5005
#define MAXLOGV 21
using namespace std;
struct edge{
	int v,next;
}e[N*2];
int tot=0;
int fa[N][MAXLOGV],h[N],dep[N],s[N],z;
int F[N];
void add(int u, int v){
	e[++tot]=(edge){v,h[u]};
	h[u]=tot;
}
void dfs(int u,int f)
{
    fa[u][0]=f;
	dep[u]=dep[f]+1;
	if (f) s[u]=1;
	else s[u]=0;
    for(int i=h[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==f) continue;
        dfs(v,u);
        s[u]++;
    }
    z^=s[u];
}
void LCA_init(int n){
    memset(fa,0,sizeof(fa));
    dep[0]=0;
    dfs(1,0);
    for(int k=0;k+1<MAXLOGV;++k){
        for(int v=1;v<=n;++v){
            if(fa[v][k]==0) fa[v][k+1]=0;
            else fa[v][k+1]=fa[fa[v][k]][k];
        }
    }
}
int LCA(int u,int v)
{
    if(dep[u]>dep[v]) swap(u,v);
    for(int k=MAXLOGV-1;k>=0;k--){ //改了順序 
        if( (dep[v]-dep[u])>>k&1){
//        	cout<<fa[v][k]<<endl;
            v=fa[v][k];
        }
    }
    if(u==v) return u;
    for(int k=MAXLOGV-1;k>=0;--k){
        if(fa[u][k]!=fa[v][k]){
            u=fa[u][k];
            v=fa[v][k];
        }
    }
    return fa[u][0];
}

int get(int x){
	return F[x]==x?x:F[x]=get(F[x]);
}
int unit(int x, int y){
	int fx=get(x);
	int fy=get(y);
	F[fx]=fy;
}
void work(int x, int y){
//	cout<<"merge "<<x<<" "<<y<<endl;
	int fx=get(x);
	int fy=get(y);
	while (fx!=fy){
//		cout<<"fx "<<fx<<" fy "<<fy<<endl;
		if (fa[fx][0]!=y){
//			cout<<"#"<<fa[fx][0]<<" "<<s[fa[fx][0]]<<"->"<<s[fa[fx][0]]-1<<endl;
			if (dep[fx]<=dep[fy]) while (1);
			z^=s[fa[fx][0]];
			s[fa[fx][0]]--;
			z^=s[fa[fx][0]];
			unit(fx,fa[fx][0]);
			fx=get(fx);
		}
		else{
			break;
		} 
	}
}
int main(){
	int n,m,a,b,x,y,u,v;
	while (scanf("%d%d%d%d%d%d",&n,&m,&a,&b,&x,&y)!=EOF){
		memset(h,-1,sizeof(h));
		tot=0;
		z=0;
		for (int i=1;i<=n;i++) F[i]=i;
		for (int i=1;i<=n-1;i++){
			scanf("%d%d",&u,&v);
			add(u+1,v+1);
			add(v+1,u+1);
		}
		LCA_init(n);
		for (int i=1;i<=m;i++){
			int nx=(a*x+b*y+z)%n;
			int ny=(b*x+a*y+z)%n;
//			cout<<"nxny "<<nx<<" "<<ny<<" "<<z<<endl; 
			x=nx;
			y=ny;
			work(x+1,LCA(x+1,y+1));
		}
		printf("%d %d\n",x,y);
	}
	return 0;
}

/*

5 10 5 5 4 0
0 1
1 2
2 3
3 4
2 2 
5 25 1 1 3 0
0 1
0 2
1 3
1 4

*/

I

這是短期內第二次使用線段樹超時了,我無話可說。

改用用了\small O(1)的記錄方法,記錄每個數字的情況,不會超時。

思路:

1. 先處理以每個數字爲結尾的最長上升子序列,再處理以每個數字爲開頭的最長上升子序列。

2. 然後從後往前,用一個數組保存後面的數字的,最長上升子序列爲i的序列的開頭最大是多少(爲了讓答案最優),每找到一個0更新這個零到之前找到的最近的一個零之前的所有情況進數組。

3. 設除去0以外的最長上升子序列的長度爲LEN,對於非零的第i個數,[ a[i]+1, l[LEN-len1[i]] -1]的數字都可以讓這個序列的最長上升子序列加一。在t數組裏O(1)標記一下就可以了。

4.注意考慮只有0的情況!!!!!!!!!!!!!!!!

#include<bits/stdc++.h>
using namespace std;
#define N 100005
int a[N],len1[N],len2[N],ans[N],l[N],t[N];
/*
struct tree{
	int t[N*4],tag[N*4];
	void clear(){
		memset(t,0,sizeof(t));
		memset(tag,0,sizeof(tag));
	}
	void push_down(int k, int l, int r, int m){
		tag[k*2]=tag[k*2+1]=1;
		t[k*2]=m-l+1;
		t[k*2+1]=r-m;
		tag[k]=0;
	}
	void update(int k){
		t[k]=t[k*2]+t[k*2+1];
	}
	void insert(int k, int l, int r, int left, int right){
		if (l>right||r<left) return;
		if (left<=l&&r<=right){
			tag[k]=1;
			t[k]=r-l+1;
			return ;
		}
		int m=(l+r)/2;
		if (tag[k]) push_down(k,l,r,m);
		insert(k*2,l,m,left,right);
		insert(k*2+1,m+1,r,left,right);
		update(k);
	} 
	int query(int k, int l, int r, int left, int right){
		if (l>right||r<left) return 0;
		if (left<=l&&r<=right)return t[k];
		int m=(l+r)/2;
		if (tag[k]) push_down(k,l,r,m);
		return query(k*2,l,m,left,right)+query(k*2+1,m+1,r,left,right);
	}
}t;
*/
int main(){
	int n;
	while (scanf("%d",&n)!=EOF){
		
		memset(t,0,sizeof(t));
		memset(l,-1,sizeof(l));
		memset(ans,0,sizeof(0));
		
		for (int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		
		int len=0;
		for (int i=1;i<=n;i++){
			if (a[i]==0){
				continue;
			}
			if (a[i]>ans[len]){
				ans[++len]=a[i];
				len1[i]=len;
			} 
			else{
				int pos=lower_bound(ans,ans+len+1,a[i])-ans;
				len1[i]=pos;
				ans[pos]=a[i];
			}
		}
		
		bool flag=false;
		long long tmp=1e7;
		for (int i=n;i>=1;i--){
			if (a[i]==0) flag=true;
			if (a[i]!=0&&len1[i]==len&&flag){
				tmp=min((long long)a[i],tmp);
			}
		}
		if (tmp<n){
//			cout<<"1+"<<tmp+1<<" "<<n<<endl;
			t[tmp+1]++;
			t[n+1]--;
		}
		
		len=0;
		ans[0]=-1e7;
		for (int i=n;i>=1;i--){
			if (a[i]==0){
				len2[i]=len;
				continue;
			}
			if (-a[i]>ans[len]){
				ans[++len]=-a[i];
				len2[i]=len;
			}
			else{
				int pos=lower_bound(ans,ans+len+1,-a[i])-ans;
				len2[i]=pos;
				ans[pos]=-a[i];
			}
			//cout<<len2[i]<<endl;
		}
		
//		cout<<"len"<<len<<endl;
		int lst=-1;
		for (int i=n;i>=1;i--){
			if (a[i]==0){
				for (int j=i+1;j<=(lst==-1?n:lst-1);j++){
					l[len2[j]]=max(l[len2[j]],a[j]);
//					cout<<len2[j]<<"->"<<l[len2[j]]<<endl;
				}
				lst=i;
				if (l[len]>1){
//					cout<<"2+"<<1<<" "<<l[len]-1<<endl;
					t[1]++;
					t[l[len]]--;
				}
			}
			else{
				if (lst!=-1&&l[len-len1[i]]>a[i]+1){
//					cout<<"3+"<<a[i]+1<<" "<<l[len-len1[i]]-1<<endl;
					t[a[i]+1]+=1;
					t[l[len-len1[i]]]-=1;
				}
			}
		}
	if (len==0){
		cout<<(n+1)*n/2<<endl;
	}
	else{
		long long  cnt=0;
		tmp=0;
		for (long long i=1;i<=n;i++){
			tmp+=t[i];
//			cout<<"tmp"<<tmp<<endl;
			cnt+=i*(len+(tmp>0?1:0));
		}
		printf("%lld\n",cnt);
	}
	}
} 

H沒找到題解且不會寫,待補

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章