【暑假集训模拟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
毒瘤题,现在也不太理解