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

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

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

单向双向V2G 环境下分布式电源与电动汽车充电站联合配置方法(Matlab代码实现)内容概要:本文介绍了在单向和双向V2G(Vehicle-to-Grid)环境下,分布式电源与电动汽车充电站的联合配置方法,并提供了基于Matlab的代码实现。研究涵盖电力系统优化、可再生能源接入、电动汽车充放电调度、储能配置及微电网经济调度等多个关键技术领域,重点探讨了在不同电价机制和需求响应策略下,如何通过智能优化算法实现充电站与分布式电源的协同规划与运行优化。文中还展示了多种应用场景,如有序充电调度、鲁棒优化模型、多目标优化算法(如NSGA-II、粒子群算法)在电力系统中的实际应用,体现了较强的工程实践价值和技术综合性。; 适合人群:具备电力系统、新能源、智能优化算法等相关背景的科研人员、研究生及从事能源系统规划与优化的工程技术人员;熟悉Matlab/Simulink仿真工具者更佳。; 使用场景及目标:①用于科研项目中关于电动汽车与分布式电源协同配置的模型构建与仿真验证;②支持毕业论文、期刊投稿中的案例分析与算法对比;③指导实际电力系统中充电站布局与能源调度的优化设计。; 阅读建议:建议结合文中提供的Matlab代码与具体案例进行同步实践,重点关注优化模型的数学建模过程与算法实现细节,同时可参考文末网盘资源获取完整代码与数据集以提升学习效率。
【电动车】【超级棒】基于蒙特卡洛模拟法的电动汽车充电负荷研究(Matlab代码实现)内容概要:本文围绕基于蒙特卡洛模拟法的电动汽车充电负荷研究展开,利用Matlab代码实现对不同类型电动汽车(如常规充电、快速充电、换电模式)在不同场景下的充电负荷进行建模与仿真。通过蒙特卡洛方法模拟大量电动汽车的充电行为,结合用户出行规律、充电时间、电量需求等随机因素,分析电动汽车规模化接入电网后对电力系统负荷的影响,并探讨分时电价策略对充电负荷的引导作用,进而优化电网运行。研究涵盖充电负荷的空间分布特性、时间分布特征及对电网峰谷差的影响,旨在为电力系统规划和电动汽车有序充电管理提供理论支持和技术工具。; 适合人群:具备一定电力系统、交通工程或新能源汽车背景的研究生、科研人员及从事智能电网、电动汽车相关领域的工程技术人员。; 使用场景及目标:①用于研究大规模电动汽车接入对配电网负荷曲线的影响;②支撑分时电价、需求响应等政策制定与优化;③为充电站规划、电网调度、储能配置等提供数据支持和仿真平台;④适用于学术研究、课题复现及工程项目前期分析。; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注蒙特卡洛模拟的参数设置、充电行为的概率建模过程,并尝试调整输入变量以观察负荷变化趋势,加深对电动汽车充电负荷不确定性和聚合效应的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值