【暑假集训模拟DAY4】DP&递推

前言

之前没写,现在补上

开局看题,T1暴力30pts秒出,但不甘心想尝试正解,结果数位DP没看出来的我自然是白给了

T2期望DP,根本没练过,先跳过(最后没时间就打了一个固输得了10pts ovo)

T3看似很复杂,实际上可以转化成普通的DP,但是没找到什么特殊的规律(还是太年轻),求出Floyd之后写了个暴力

T4看到分值分布就觉得得分希望不大(两个subtask,分别是35pts和65pts),加上没有好的做法就放弃了

期望得分:30+10+30+0=70pts

实际得分:30+10+25+0=65pts

好吧T3稍微TLE了一个点,也能接受

虽然分低但好歹没挂分(这能挂了那我也是重量级)

看题吧

题解

T1 lecture

不会数位DP的小丑竟是我自己

我应该是全机房唯一没看出来是数位DP的(这明明是数位DP裸题啊喂!),因为总共就做过两道,还是2个月前的某一天

不过DP状态倒是和数位DP状态设的一模一样

不过这题有点不同,从低位到高位好写一些,因为要判断后缀是否整除,会更方便一些

复习了一下数位DP的写法,还是挺不错的(挖坑,还是要多练几道数位熟悉熟悉)坑已填好=w=

(回看博客发现上面说了一堆废话)

代码中用 zero 表示后导零有没有结束,check 表示有没有整除 k 的后缀,这些都是为了判断删除前缀之后后缀不能为0(例如:10000->0000是不可以的),zero 不同的时候记忆化的结果也不同,所以只记忆化zero==0的情况(因为zero==1的情况很少),可以写出以下代码

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
int f[1005][105][2],n,k,m,mi[1005];
int dp(int pos,int res,int check,int zero)
{
	if(pos>n) return check;
	if(!zero&&f[pos][res][check]!=-1) return f[pos][res][check];
	int ans=0;
	for(int i=0;i<=9;i++)
	{
		if(!i&&pos==n) continue;
		int tmp=(res+i*mi[pos-1]%k)%k;
		if(check) ans=(ans+dp(pos+1,tmp,check,zero&&!i))%m;
		else ans=(ans+dp(pos+1,tmp,!tmp&&!(zero&&!i),zero&&!i))%m;
	}
	if(!zero) f[pos][res][check]=ans;
	return ans;
}
int main()
{
	memset(f,-1,sizeof(f));
	scanf("%d%d%d",&n,&k,&m);
	mi[0]=1;
	for(int i=1;i<=n;i++) mi[i]=(mi[i-1]*10)%k;
	printf("%d",dp(1,0,0,1));
	return 0;
}

T2 nthlon

考场看到期望直接跪了好吗...直接弃了

期望DP主要利用的也就是期望的线性性和对称性,还有期望和概率的关系

翻译过来就是期望可以分别算加在一起,每个人的期望相同,算期望一般先算概率

这里就是一个类似背包的转移,不过这里用了前缀和优化很巧妙,虽然看起来很简单但实际上不容易发现

这里 m-1 个人的期望值相同,所以算一个人得分为 i 的概率就好,最后乘上(m-1)就是得分为 i 的期望人数

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,M = 1e5+10;
double dp[105][M],sum[M];
int a[105];
int n,m,s;
double expd;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),s+=a[i];
	if(m==1) {printf("%.16lf",1.0);return 0;}
	dp[0][0]=sum[0]=1;
	double base=1.0/(m-1);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=min(s-1,i*m);j++)
		{
			int l=max(i-1,j-m),r=min(j-1,m*(i-1));
			dp[i][j]+=base*sum[r];
			if(l>0) dp[i][j]-=base*sum[l-1];
			if(l<=j-a[i]&&j-a[i]<=r) dp[i][j]-=base*dp[i-1][j-a[i]];
		}
		for(int j=1;j<=min(s-1,i*m);j++) sum[j]=sum[j-1]+dp[i][j];
	}
	for(int i=1;i<s;i++) expd+=dp[n][i];
	expd*=m-1;
	printf("%.16lf",expd+1);
	return 0;
}

T3 Assignment

分段DP好题啊!

首先,所有所需要的路径一定是总部到所有点和所有点到总部的距离,所以不用Floyd,可以用正反两次dijkstra预处理求出

然后问题可以转化成求sigma(集合中的元素值*(集合大小-1)),由于“经验”可以发现选出的元素大小连续排布,所以需要排序,可以分段DP

但是复杂度还是不够好,又发现选出的序列长度单调不升,第三层 k 可以减少循环次数,其中(1+1/2+1/3+...+1/n)=logn,总复杂度O(nlogn)

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 5005,M = 50005;
#define pr pair<ll,int>
#define mp make_pair
priority_queue<pr,vector<pr>,greater<pr> >q;
int n,b,s,m,ecnt1,ecnt2,head1[M*2],head2[M*2],vis[N];
ll dis[N],x[N];
ll sum[N],dp[N][N];
struct edge
{
	int to,nxt,w;
}a1[M*2],a2[M*2];
void add1(int x,int y,int w)
{
	a1[++ecnt1]=(edge){y,head1[x],w};
	head1[x]=ecnt1;
}
void add2(int x,int y,int w)
{
	a2[++ecnt2]=(edge){y,head2[x],w};
	head2[x]=ecnt2;
}
void init()
{
	memset(head1,-1,sizeof(head1));
	memset(head2,-1,sizeof(head2));
	memset(x,0,sizeof(x));
}
void dijk1()
{
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[b+1]=0; 
	q.push(mp(0,b+1));
	while(!q.empty())
	{
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head1[u];~i;i=a1[i].nxt)
		{
			int v=a1[i].to;
			if(dis[v]>dis[u]+a1[i].w)
			{
				dis[v]=a1[i].w+dis[u];
				q.push(mp(dis[v],v));
			}
		}
	}
}
void dijk2()
{
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[b+1]=0; 
	q.push(mp(0,b+1));
	while(!q.empty())
	{
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head2[u];~i;i=a2[i].nxt)
		{
			int v=a2[i].to;
			if(dis[v]>dis[u]+a2[i].w)
			{
				dis[v]=a2[i].w+dis[u];
				q.push(mp(dis[v],v));
			}
		}
	}
}
int main()
{

	while(scanf("%d%d%d%d",&n,&b,&s,&m)!=EOF)
	{
		init();ecnt1=0;ecnt2=0;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add1(u,v,w);
		add2(v,u,w);
	}
	dijk1();
	for(int i=1;i<=b;i++) x[i]+=dis[i];
	dijk2();
	for(int i=1;i<=b;i++) x[i]+=dis[i];

	sort(x+1,x+b+1);
//	for(int i=1;i<=n;i++)
//		printf("x[%d]=%d\n",i,x[i]);
	for(int i=1;i<=b;i++) sum[i]=sum[i-1]+x[i];
	for(int i=1;i<=b;i++)
		for(int j=1;j<=s;j++) dp[i][j]=1e16;
	//dp[1][0]=0;
	//dp[1][1]=x[1];
	for(int i=1;i<=b;i++) dp[i][1]=sum[i]*(i-1);
	for(int i=1;i<=b;i++)
		for(int j=2;j<=s;j++)
			for(int k=i-i/j;k<i;k++)
			{
				dp[i][j]=min(dp[k][j-1]+(sum[i]-sum[k])*(i-k-1),dp[i][j]);
				//前i个元素划分成j段,[1,k],[k+1,i] 
				//printf("dp[%d][%d]=%d\n",i,j,dp[i][j]);
			}
	printf("%lld\n",dp[b][s]);

}
	return 0;
}
/*
5 4 2 10
5 2 1
2 5 1
3 5 5
4 5 0
1 5 1
2 3 1
3 2 5
2 4 5
2 1 1
3 4 2
*/

T4 recoverset

毒瘤题,现在也不太理解

posted @ 2021-08-21 23:21  conprour  阅读(33)  评论(0编辑  收藏  举报