8.12模拟:dp&递推

前言

245分
100+70+40+35
不太满意qwq
被KH暴打
后三道题全是部分分
而且T2和T3关键性质都出来了,距离正解只差临门一脚
但是就差了这一脚
感觉今天T2、T3全是折在了刷表法上呢…
T4后面的65考场上确实不太可做
但至少现在挂分少了很多吧

本次最大收获

刷表法的记忆化搜索复杂度是假的
血的教训!

考场

开局先遍历
T1数位裸题
T2第一看觉得有点恶心(主要是期望dp做的太少了)
T3乍一看:字好多(第一次遍历不咋动脑子,基本字多的我都看不出来啥…)
T4题面和数据点分布似乎都在说本题您会爆零

和昨天一样,先把签到题T1切了,稳一下心态
有一说一这个数位比YBT的好多阴间数位友善多了
后导零的处理和正常略有不同,调了一会
8:45左右过了样例3,基本没问题了

因为T2T3主观上没有太大倾向,所以就决定先看T2
仔细想想T2的转移还是很好出来的
但是朴素转移明显比正解差了一维
抱着脑袋神游了矩阵快速幂等神仙操作
然并卵
70也算不错了
就去了T3

T3就是看着字挺多其实抽象出来很简单的题
给一个集合,要把它分成s份,每份代价是西格玛*(size-1)
求最小代价
很容易想到他肯定是要sort一下然后约取越短
但直接转移显然是n3的,因为没有利用到越取越短的性质,只有65
所以我就想到了记忆化搜索,利用不能比上一次取得长剪枝
但填表记忆化搜索不太好转移这个诡异的状态
于是开始刷表法的记忆化搜索
(伏笔)

去到T4,大概9:50左右
看s的大小那么小,似乎还是有搞头的
想到可以从小到大从背包取答案并不断从方案背包里删除这个元素
很愉快的拿到了35并且把我继续做65的路子给堵死了
其实负值时想过从最大值走
但是想到可能是减正的或者加负的两种情况无法判断就放弃了
忽略了其中绝对值的统一性

全做完大概10:50左右
时间剩的比较少了
毕竟这次写的分明显不太够
我想扣一道正解出来
但连检查带想的从头到尾遍历了一遍,还是没什么思路
时间就过去了大半
时间到了最后半个多小时我甚至都不太敢想正解了
因为感觉想出来也没时间写了qwq
就这样在矛盾中把时间浪费完了

复盘

事实是T2T3要是正解出来了加起来改过也不用20min
就是差一点qwq

T1 lecture

确实是比较裸的数位
连求[l,r]的答案这种东西都没有
有些不同的地方是因为要整除k的后缀所以从低位到高位填
(这紫题就离谱)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+100;
int n,mod,k;
int dp[1050][105][2];//dp[pos][mod][op]
int mi[1050];
void print(int k){
	for(int i=1;i<k;i++) printf("  ");
}
int find(int pos,int res,int op,int zero){
//	print(pos);printf("pos=%d res=%d op=%d zero=%d\n",pos,res,op,zero);
	if(pos>n){
		return op ? 1 : 0;
	}
	if(!zero&&dp[pos][res][op]!=-1) return dp[pos][res][op];
	int ans=0;
	for(int i=0;i<=9;i++){
		if(pos==n&&i==0) continue;
		int nres=(res+i*mi[pos-1])%k;
		int nz=zero&&i==0;
		if(op){
			ans+=find(pos+1,nres,1,nz);
			ans%=mod;
		}
		else{
			int nop= nres==0&&nz==0 ?1:0;
//			print(pos+1);printf("i=%d nres=%d nop=%d ans=%d\n",i,nres,nop,ans);
			ans+=find(pos+1,nres,nop,nz);
			ans%=mod;
		}
	}
	if(!zero) dp[pos][res][op]=ans;
//	print(pos);printf("return: pos=%d res=%d op=%d zero=%d ans=%d\n",pos,res,op,zero,ans);
	return ans;
}
int main(){
//	freopen("lecture.in","r",stdin);
//	freopen("lecture.out","w",stdout);
	memset(dp,-1,sizeof(dp));
	scanf("%d%d%d",&n,&k,&mod);
	mi[0]=1;
	for(int i=1;i<=n;i++){
		mi[i]=(mi[i-1]*10)%k;
	}
	printf("%d\n",find(1,0,0,1));
	return 0;
}
/*

*/

T2 n-thlon

就是这么转移的啊!!!
我的注意力全在优化dp设计和转移方法上了
我那个代码瓶颈的for循环那么裸的从头加到尾
我愣是没看出来前缀和
枯了
也和我用刷表法相对不太好看出来有点关系
不好看出来个头啊

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
int n,m;
double dp[105][N],sum[105][N],per;
int r[105],tot;
int mx,nmx;
double ask(int k,int p){
	return p<0?0:sum[k][p];
}
int main(){
	//freopen("nthlon.in","r",stdin);
	//freopen("nthlon.out","w",stdout);
	//printf("%d\n",sizeof(dp)/1024/1024);
	scanf("%d%d",&n,&m);
	if(m==1){
		printf("%.16lf\n",1.0);
		return 0;
	}
	per=1.0/(m-1);
	for(int i=1;i<=n;i++){
		scanf("%d",&r[i]);
		tot+=r[i];
	}
	dp[0][0]=1;
	for(int i=0;i<=tot;i++) sum[0][i]=1;
	for(int k=1;k<=n;k++){
		//nmx=0;
		for(int i=0;i<=tot;i++){
			dp[k][i]=(ask(k-1,i-1)-ask(k-1,i-m-1)-(ask(k-1,i-r[k])-ask(k-1,i-r[k]-1)))*per;
		}
		for(int i=1;i<=tot;i++){
			sum[k][i]=sum[k][i-1]+dp[k][i];
		}
	//	mx=max(mx,nmx);
		//for(int i=1;i<=tot;i++) printf("k=%d i=%d dp=%lf\n",k,i,dp[k][i]);
	}
	double ans=0.0;
	for(int i=1;i<tot;i++){
		ans+=dp[n][i];
	}
	ans*=(m-1);
	printf("%.16lf",ans+1.0);
	return 0;
}
/*
4
10
2
1
2
1

5 5
1 2 3 4 5

3 6
2 4 2
*/

T3 assignment

这个也就差一点

sert和越取越短的性质我都想到了
但是我没用好这个性质
直接反向优化了
我那个刷表的记忆化搜索复杂度假了
因为它相当于每更新一次答案都会往后一直到尾尝试一次更新
这复杂度直接裂开
再剪枝也没有用
还不如不优化,只剩下40(换句话说就是稍微大点的数据全T了)
其实我们因为越取越短的性质,[0,i] 分k短的最优解最后一段长度肯定不超过i/k下取整
所以复杂度大概是:

nn/1+nn/2+nn/3…nn/k

欧拉老爷子告诉我们,这个东西大概是

n*nlogn

然后就可解了

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e3+100;
const int M=5e4+100;
typedef pair<int,int>pr;
#define mp make_pair
int n,b,s,m;
struct node{
	int to,nxt,v;
}p1[M],p2[M];
int fi1[N],fi2[N],cnt1,cnt2;
void addline1(int x,int y,int v){
	p1[++cnt1]=(node){y,fi1[x],v};
	fi1[x]=cnt1;
}
void addline2(int x,int y,int v){
	p2[++cnt2]=(node){y,fi2[x],v};
	fi2[x]=cnt2;
}
int dis1[N],dis2[N];
int jd1[N],jd2[N];
priority_queue<pr,vector<pr>,greater<pr> >q;
void dij1(){
	memset(dis1,0x3f,sizeof(dis1));
	dis1[b+1]=0;	
	while(!q.empty()) q.pop();
	q.push(mp(0,b+1));
	while(!q.empty()){
		int now=q.top().second;q.pop();
		if(jd1[now]) continue;
		jd1[now]=1;
		for(int i=fi1[now];~i;i=p1[i].nxt){
			int to=p1[i].to;
			if(dis1[to]>dis1[now]+p1[i].v){
				dis1[to]=dis1[now]+p1[i].v;
				q.push(mp(dis1[to],to));
			}
		}
	}
	return;
}
void dij2(){
	memset(dis2,0x3f,sizeof(dis2));
	dis2[b+1]=0;	
	while(!q.empty()) q.pop();
	q.push(mp(0,b+1));
	while(!q.empty()){
		int now=q.top().second;q.pop();
		if(jd2[now]) continue;
		jd2[now]=1;
		for(int i=fi2[now];~i;i=p2[i].nxt){
			int to=p2[i].to;
			if(dis2[to]>dis2[now]+p2[i].v){
				dis2[to]=dis2[now]+p2[i].v;
				q.push(mp(dis2[to],to));
			}
		}
	}
	return;
}
ll v[N],sum[N];
ll dp[N][N],ans=2e15;//dp[pos][lft]
void dfs(int pos,int lft,int lst,ll val){
	if(dp[pos][lft]!=-1&&dp[pos][lft]<=val) return;
	else dp[pos][lft]=val;
	if(b-pos>lft*lst) return;
	if(pos>=n) return;
	if(lft==1){
		ans=min(ans,val+(sum[b]-sum[pos])*(b-pos-1));
		return;
	}
	for(int i=lst;i>=1;i--){
		dfs(pos+i,lft-1,i,val+(sum[pos+i]-sum[pos])*(i-1));
	} 
	return;
}
int main(){
//	freopen("assignment.in","r",stdin);
//	freopen("assignment.out","w",stdout);
	memset(fi1,-1,sizeof(fi1));
	memset(fi2,-1,sizeof(fi2));
	scanf("%d%d%d%d",&n,&b,&s,&m);
	int x,y,z;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		addline1(x,y,z);
		addline2(y,x,z);
	}
	dij1();
	dij2();
	for(int i=1;i<=b;i++) v[i]=dis1[i]+dis2[i];//printf("i=%d v=%lld\n",i,v[i]);
	sort(v+1,v+1+b);
	for(int i=1;i<=b;i++) sum[i]=sum[i-1]+v[i];
	for(int i=1;i<=n;i++) dp[i][1]=sum[i]*(i-1);
	for(int k=2;k<=s;k++){
		for(int i=k;i<=b;i++){
			dp[i][k]=2e15;
			for(int l=1;l<=i/k;l++){
				dp[i][k]=min(dp[i][k],dp[i-l][k-1]+(sum[i]-sum[i-l])*(l-1));
			}
		}
	}
	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

5 4 2 10
5 2 1
2 5 1
3 5 5
4 5 10
1 5 1
2 3 1
3 2 5 
2 4 5 
2 1 1
3 4 2

5 4 3 9
5 2 1
2 5 10
5 1 3
1 5 0
2 1 1
5 3 5
3 5 5
5 4 1000
4 2 0
*/

T4 recoverset

大黑题确实不太可做
从我考场上卡住的地方继续的话也不远了
我们发现最大值减次大值就是当前绝对值最小的元素的绝对值
然后我们如果就把它当正的处理,整个dp序列会向左平移vi的绝对值
因为是平移,相对关系不变,我们可以继续用最大减次大求下一个的绝对值
如法炮制后所有答案的绝对值就出来了
接下来添符号
首先,任务1全是非负时
所以元素全减完背包里应该剩个空集0
显然带负数是应该也是一样的
但是由于我们每次都当正的算整体向左移
所以这个空集早就负的没影了
我们加符号的任务就是把这个零点移动回到0的位置
把一个vi转负会使零点右移v[i]的单位
所以我们的合法方案其实就是用答案绝对值,凑出当前这个零点的绝对值
也就是个硬币背包
至于字典序最小的问题我们可以贪心的先尝试让绝对值最大的数加负号即可

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+100;
int n,mod,k;
struct node{
	ll x,num;
	bool operator < (const node y)const{return x<y.x;}
}p[N];
map<ll,int>mp;
ll dp[N];
ll ans[N],tot,num;
bool jd[70][100020];
int main(){
	int t,cnt=0;
	//printf("%d",sizeof(jd)/1024/1024);
	scanf("%d",&t);
	while(t--){
		cnt++;tot=0;mp.clear();
		scanf("%d",&n);
		for(int i=1;i<=n;i++) scanf("%lld",&p[i].x);
		for(int i=1;i<=n;i++) scanf("%lld",&p[i].num);
		sort(p+1,p+1+n);
		for(int i=1;i<=n;i++){
			mp[p[i].x]=i;
			dp[i]=p[i].num;
		}
		int pl=n,nxt;
		while(pl){
			nxt=dp[pl]>1?pl:pl-1;
			while(nxt&&!dp[nxt]) nxt--;
			if(nxt==0) break;
			ans[++tot]=p[pl].x-p[nxt].x;
			ll now=ans[tot];
			//printf("pl=%d nxt=%d tot=%d now=%d\n",pl,nxt,tot,now);
			for(int i=1;i<=n;i++){
				if(now==0){
					dp[i]/=2;continue;
				}
				ll pre=p[i].x-now;
				int id=mp[pre];
				if(!id) continue;
				dp[i]-=dp[id];
			//	printf("i=%d id=%d dp=%d\n",i,id,dp[i]);
			}
			pl=nxt;
			while(pl&&!dp[pl]) pl--;
		}
		if(p[pl].x!=0){
		//	printf("x=%lld\n",p[pl].x);
			num=n;
			memset(jd,0,sizeof(jd));
			jd[0][mp[0]]=1;
			for(int i=0;i<tot;i++){
				for(int j=1;j<=num;j++){
					if(jd[i][j]==0) continue;
					jd[i+1][j]=1;
					int id=mp[p[j].x+ans[i+1]];
					if(!id){
						mp[p[j].x+ans[i+1]]=++num;id=num;p[num].x=p[j].x+ans[i+1];
					}
					jd[i+1][id]=1;
				}
			}
//			for(int i=1;i<=tot;i++){
//				for(int j=1;j<=num;j++) printf("i=%d j=%d x=%lld jd=%d\n",i,j,p[j].x,jd[i][j]);
//			}
			ll now=-p[pl].x;
			for(int i=tot;i>=1;i--){
			//	printf("i=%d now=%lld\n",i,now);
				for(int j=1;j<=num;j++){
			//		printf("  j=%d x=%lld jd=%d\n",j,p[j].x,jd[i-1][j]);
				}
				if(jd[i-1][mp[now-ans[i]]]){
					now-=ans[i];
					ans[i]=-ans[i];
				}
			}
			sort(ans+1,ans+1+tot);
		}
		printf("Case #%d: ",cnt);
		for(int i=1;i<=tot;i++) printf("%lld ",ans[i]);
		printf("\n");
	}
	return 0;
}
/*
3
8
0 1 2 3 4 5 6 7
1 1 1 1 1 1 1 1
4
0 1 3 4
4 4 4 4
5
-2 -1 0 1 2
1 2 2 2 1

1
6
-4 -2 -3 -1 0 1
2 4 2 4 2 2
*/

总结

好的地方是我现在看题的思路似乎逐渐靠谱了
T234其实都有了正解的模样
只是中间推到某步卡住了
(行百里者半九十)
还是有点可惜
所以以后我想题可能得适当的加深我思考的迭代搜索深度
遇见瓶颈别着急回溯,哪一下子突破了说不定就开朗了
但也不能一概而论,迭代深度太深可能会使思路中错误的搜索树过大
还是得积累比赛经验慢慢来吧

明天:杂题
真正的盲考了
这就很容易跑题了
千万不能贪
加油!awa

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值