CCPC2018 湖南全国邀请赛补题(DP/并查集/最长上升子序列)(H待补)

博客内容涉及CCPC2018湖南全国邀请赛的补题解析,主要讨论了D题的DP解决方案和E题的并查集应用。D题通过DP计算环中黑白段的组合;E题比赛中误用树链剖分,赛后发现并查集更合适,利用异或处理边的变更。此外,还提及I题的最长上升子序列问题,最初因线段树导致超时,改用记录方法避免了此问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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没找到题解且不会写,待补

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值