24.4.21(链表模拟,板刷dp)(天梯赛)

星期一:

补牛客周赛39 D                                    牛客传送门

思路:这题数据不够强,dp【i】表示价值总和为 i 的物品最少数量,跑到100*p的价值,取p到100p中最少物品数,能过

正解是bfs,map存的是单个物品价值,vi存的是bfs到达的点,每次跑一遍p,能去但没去的点就继续存入队列

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int p;
int a[N];
bool vi[N];
void solve(){
	cin >> n >> p;
	queue<PII>qu;
	map<int,int>mp;
	for(int i=1;i<=n;i++){
		int x; cin >> x;
		if(!vi[x%p]){
			qu.push({x%p,1});
			vi[x%p]=1;                //对到过的点进行标记
			mp[x%p]=1;                //对物品价值进行标记
		}
	}
	while(1){
		auto [x,y]=qu.front(); qu.pop();
		if(!x){
			cout << y;
			return ;
		}
		for(int i=0;i<p;i++){
			if(!mp[i] || vi[(x+i)%p]) continue;
			vi[(x+i)%p]=1;
			qu.push({(x+i)%p,y+1});
		}
	}
}

补牛客周赛round40 D                              牛客传送门

思路:dp【i】表示花费 i 金币可达到的最大战力

刚开始不知道购买和升级的关系怎么处理

多重背包,前两维正常枚举,第三维枚举升级数,顺带着购买金币p【i】和初始战力a【i】就行了

代码如下:

ll n;
int x;
int a[330],p[330],c[330],u[330],l[330];
ll dp[330];
void solve(){
	cin >> n >> x;
	for(int i=1;i<=n;i++)
		cin >> a[i] >> p[i] >> c[i] >> u[i] >> l[i];
	for(int i=1;i<=n;i++){
		for(int j=x;j>=p[i];j--)
			for(int k=0;k<=l[i] && k*c[i]<=j-p[i];k++)             //枚举升多少级
				dp[j]=max(dp[j-p[i]-k*c[i]]+a[i]+k*u[i],dp[j]);
            //for(int k=min(l[i],(j-p[i])/c[i]);k>=0;k--)          //k倒着枚举也行
				//dp[j]=max(dp[j-p[i]-k*c[i]]+a[i]+k*u[i],dp[j]);
	}
	cout << dp[x];
}

dp题单 数位dp第四题                                   cf传送门

思路:很刁的一道数位dp                               参考题解

dp【now】【num】【lcm】表示当前枚举到了数字的第now位,枚举到上一位的数是num,所有枚举数字的最小公倍数为lcm,dfs的第四个参数 if1表示目前枚举是否在数字的上界

首先解释下dp数组的维度大小,第一维是数字长度,不超过20

第二位是数字大小,因为1-9的公倍数为2520,所以 结果数%公倍数,和结果数先模2520,再%公倍数的结果是一样的,所以边累加边对2520取模

第三维是枚举数字的公倍数,1-9的公倍数是2520,可能会mle,实际上2520的因数只有50个不到,所以可以开个数组或mp映射一下,第三维开50就够了

然后要注意的一点是,记忆化处理时要注意上界状态,只有非上界状态才能记忆化处理

代码如下:

ll n;
ll dp[20][2522][55];
int lc[2522],a[20],cnt;
ll dfs(int now,int num,int lcm,bool if1){
	if(!now){
		if(num%lcm==0) return 1;
		return 0;
	}
	if(!if1 && dp[now][num][lc[lcm]]!=-1)          //如果在上界状态,不能直接结算
		return dp[now][num][lc[lcm]];              //记忆化处理
	int ma=9;                                      //当前位枚举的数字
	if(if1) ma=a[now];                             //若在上界,则不得超过a[now]
	ll res=0;
	for(int i=0;i<=ma;i++)
		res+=dfs(now-1,(num*10+i)%2520,i?i*lcm/__gcd(i,lcm):lcm,if1 && i==ma);
	if(!if1) dp[now][num][lc[lcm]]=res;            //若在上界状态,不能记忆化记录答案
	return res;
}
void solve(){
	int t; cin >> t;
	for(int i=1,j=0;i<=2520/i;i++)
		if(2520%i==0) lc[i]=++j,lc[2520/i]=++j;
	memset(dp,-1,sizeof dp);                       //记忆化处理
	while(t--){
		ll l,r; cin >> l >> r;
		ll tl=l-1,tr=r;
		cnt=0;
		while(tl) a[++cnt]=tl%10,tl/=10;
		ll resl=dfs(cnt,0,1,1);
		cnt=0;
		while(tr) a[++cnt]=tr%10,tr/=10;
		ll resr=dfs(cnt,0,1,1);
		cout << resr-resl << "\n";
	}
}

星期二:

每日构造(bushi                                          cf传送门

题意:将1至n*m的数,填入n*m的方格中,使得相邻方格填入值的差绝对值大于1

思路:首先n若小于m,交换n和m的值,方便判断和思考情况

-1的情况三种,(n==1 && (m==2 || m==3)) || (n==2 && m==2)

然后分为了两种情况构造,n只有一行,先顺着放偶数,再顺着放奇数

n>1,将第一行的偶数放到最后一行的偶数格,其余行偶数放上一行的偶数格

最后输出时,记得判断是否有交换n和m的值

代码如下:

ll n;
ll m;
int a[110][110];
void solve(){
	cin >> n >> m;
	bool if1=0;
	if(n>m) swap(n,m),if1=1;
	if(n==1 && m==1){cout << 1; return ;}
	if((n==1 && m<=3) || (n==2 && m==2)){cout << -1; return ;}
	if(n==1){
		for(int j=2;j<=m;j+=2) cout << j << " ";
		for(int j=1;j<=m;j+=2) cout << j << " ";
		return ;
	}
	for(int j=1;j<=m;j++){
		if(j&1) a[1][j]=j;
		else a[n][j]=j;
	}
	int num=m+1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(j&1) a[i][j]=num++;
			else a[i-1][j]=num++;
		}
	}
	if(!if1){
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++) cout << a[i][j] << " \n"[j==m];
	}else{
		for(int j=1;j<=m;j++)
			for(int i=1;i<=n;i++) cout << a[i][j] << " \n"[i==n];
	}
}

同场顺便做了道数据结构:                                 cf传送门

思路:模拟链表+优先队列

用链表来模拟删除操作,第一次把挨着的BG全丢队列里,然后用优先队列跑,离队时,链表删除两节点,若接上的是BG,继续丢队列里

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
string s;
int a[N],pre[N],nex[N];
bool vi[N];
struct nod{
	int idl,idr,ab;
	bool operator < (const nod &a) const{       //重载运算符
		return a.ab==ab?a.idl<idl:a.ab<ab;
	}
};
void solve(){
	cin >> n >> s;s=" "+s;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		pre[i]=i-1;
		nex[i]=i+1;
	}
	priority_queue<nod>pq;                      //优先队列维护差值最小的一对
	for(int i=1;i<n;i++)
		if(s[i]!=s[i+1]) pq.push({i,i+1,abs(a[i]-a[i+1])});
	queue<PII>ans;
	while(!pq.empty()){
		auto t=pq.top(); pq.pop();
		if(vi[t.idl] || vi[t.idr]) continue;        //若两人中已有人离队,跳过
		ans.push({t.idl,t.idr});
		vi[t.idl]=vi[t.idr]=1;
		nex[pre[t.idl]]=nex[t.idr];
		pre[nex[t.idr]]=pre[t.idl];                      //链表的删除操作
		if(pre[t.idl]<1 || nex[t.idr]>n) continue;
		if(s[pre[t.idl]]!=s[nex[t.idr]])                //若重拼接的俩字符符合条件
			pq.push({pre[t.idl],nex[t.idr],abs(a[pre[t.idl]]-a[nex[t.idr]])});
	}
	cout << ans.size() << "\n";
	while(!ans.empty()){
		auto [x,y]=ans.front(); ans.pop();
		cout << x << " " << y << "\n";
	}
}

下午天梯模拟赛,还行,竟然还能复习下kmp板子

星期三:

dp题单 数位dp第五题                                           cf传送门

思路:dp【i】【j】【k】【y】表示考虑到第 i 位数,目前数字%m后为 j,奇偶,前面是否一直为0

dfs多了一个参数 ifm 表示是否为上界状态

这里给出的a和b为字符串形式,不采用让a-1的方案,这涉及高精度,以及a字符串的长度可能改变,于是直接判断a是否符合条件,符合则最后答案+1

因为填数的要求与位数的奇偶性有关,若前面一直为0,则此位数的奇偶性应该一直为1,且因num不断取模,无法根据num判断前面是否填过数,所以用 ifn判断

然后注意上界状态不能记忆化处理

代码如下:

ll n;
int m,d;
string a,b;
ll dp[2020][2020][2][2];
int c[2020];
ll dfs(int now,int num,bool if1,bool ifm,bool ifn){
	if(now>n) return num==0;                       //%m==0即符合条件
	if(!ifm && dp[now][num][if1][ifn]!=-1)
		return dp[now][num][if1][ifn];
	ll res=0;
	if(!ifn) res+=dfs(now+1,0,if1,0,0);          //若前面一直为0,可以接着填0,奇偶性不变
	if(if1){                                     //奇数位填数
		int ma=9;
		if(ifm) ma=c[now];
		for(int i=ifn==0;i<=ma;i++){             //填过数,i可以从0开始,否则1开始
			if(i==d) continue;
			res+=dfs(now+1,(num*10+i)%m,if1^1,ifm&&i==ma,1),res%=mod;
		}
	}else{                                       //偶数位填数
		if(ifm && c[now]<d) res=0;
		else res+=dfs(now+1,(num*10+d)%m,if1^1,ifm&&d==c[now],1),res%=mod;
	}
	if(!ifm) dp[now][num][if1][ifn]=res;           //记忆化处理
	return res;
}
void solve(){
	cin >> m >> d;
	cin >> a >> b;
	n=a.size();
	a=" "+a,b=" "+b;
	bool ifa=1;ll numa=0;                    //判断a是否符合条件
	for(int i=1;i<=n;i++){
		if(i&1 && a[i]-'0'==d) ifa=0;
		if(!(i&1) && a[i]-'0'!=d) ifa=0;
		numa=(numa*10+a[i]-'0')%m;
	}
	if(numa) ifa=0;
	for(int i=1;i<=n;i++) c[i]=a[i]-'0';
	memset(dp,-1,sizeof dp);                       //记忆化处理
	ll res1=dfs(1,0,1,1,0); if(ifa) res1--;
	for(int i=1;i<=n;i++) c[i]=b[i]-'0';
	ll res2=dfs(1,0,1,1,0);
	ll ans=(res2-res1+mod)%mod;
	cout << ans;
}

atcoder ABC349 D                                       atc传送门

思路:纯贪心

每次分开的区间长度其实就是2的整数次幂,所以直接贪心的放即可

代码如下:

ll n;
void solve(){
	ll l,r; cin >> l >> r;
	vector<PII>ans;
	while(l<r){
		ll i=1,j=l;
		while(!(j&1) && l+i<=r) j>>=1,i<<=1;
		while(l+i>r) i>>=1,j<<=1;
		ans.push_back({l,l+i});
		l+=i;
	}
	cout << ans.size() << "\n";
	for(auto i:ans) cout << i.first << " " << i.second << "\n";
}

星期四:

每日构造(2200                                                 cf传送门

题意:给定一1到n升序排列的permutation,k,长度为m的数组b

操作为选定k个数,除了这k个数的中位数,其余数删去,问能否通过任意次操作得到数组b

思路:先说结论,若存在 bi 前后都有 大于等于 (k-1)/2 个被删数则YES,否则NO,如何证明还没想明白

dp题单 区间dp第二题 括号涂色(区间dp好题啊             cf传送门

0代表无色,1代表蓝色,2表示红色

思路:dp【l】【r】【i】【j】表示涂好 l 到 r ,l 涂 i 色,r 涂 j 色的方案数

初始化把每个(对应的)下标记录,用stack实现

区间dp用dfs递归实现,因为s的括号必两两对应,所以dfs的 l 和 r 从1,n开始,l 必定是(,r必定是),l,r的对应分三种情况,一是 r==l + 1,直接赋值即可  ,  二是 l 和 r 对应,但不相邻,枚举 l+1 和 r-1 的涂色方案,把合法状态加上即可,  三是不对应,则需要把 l 和 ma【l】,ma【l】+1和 r 两个对应的区间的方案算出,两区间方案数相乘

代码如下:

ll n;
string s;
stack<int>sk;
int ma[777];
ll dp[777][777][3][3];
void init(){
	for(int i=1;i<=n;i++){
		if(s[i]=='(') sk.push(i);
		else ma[sk.top()]=i,sk.pop();
	}
}
void dfs(int l,int r){
	if(l==r-1){
		dp[l][r][0][1]=dp[l][r][0][2]=dp[l][r][1][0]=dp[l][r][2][0]=1;
		return ;
	}
	if(ma[l]==r){
		dfs(l+1,r-1);
		for(int i=0;i<3;i++){
			for(int j=0;j<3;j++){
				if(j!=1) dp[l][r][0][1]+=dp[l+1][r-1][i][j],dp[l][r][0][1]%=mod;
				if(j!=2) dp[l][r][0][2]+=dp[l+1][r-1][i][j],dp[l][r][0][2]%=mod;
				if(i!=1) dp[l][r][1][0]+=dp[l+1][r-1][i][j],dp[l][r][1][0]%=mod;
				if(i!=2) dp[l][r][2][0]+=dp[l+1][r-1][i][j],dp[l][r][2][0]%=mod;
			}
		}
	}else{
		dfs(l,ma[l]);
		dfs(ma[l]+1,r);
		for(int i=0;i<3;i++){
			for(int j=0;j<3;j++){
				for(int p=0;p<3;p++){
					if(p>0 && p==j) continue;
					for(int q=0;q<3;q++){
						dp[l][r][i][q]+=dp[l][ma[l]][i][j]*dp[ma[l]+1][r][p][q];
						dp[l][r][i][q]%=mod;
					}
				}
			}
		}
	}
}
void solve(){
	cin >> s;
	n=s.size(),s=" "+s;
	init();
	dfs(1,n);
	ll ans=0;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			ans+=dp[1][n][i][j],ans%=mod;
	cout << ans;
}

星期五:

这两天都在准备星期六的天梯赛,没啥可放的题

小白月赛91 D:半小时出思路,代码写出来又调了半小时,感觉不该调这么久

                                                                牛客传送门

思路:想着计算方案数和二进制有关,所以用二进制的思路考虑了下

发现一个规律,当 i 位上为偶数,计算与前面组合的方案时,前面可以看作一个长度 i-1 的二进制数,每位上如果为0就为0,否则为1,那么方案数就能+=此二进制数

因为在组合偶数时,只有0开头的数不能选,为0,j 位数为头 i 位数为尾的方案为2的i-j-1次幂,所以可以用一个变量当作 "二进制前缀和",实现O(1)计算

代码如下:

ll n;
string s;
void solve(){
	cin >> n >> s;s=" "+s;
	ll ans=0,num=0;
	for(int i=1;i<=n;i++){
		int t=s[i]-'0';
		if(!t){
			ans++;
			ans+=num,ans%=mod;
			num<<=1,num%=mod;
		}else if(t&1){
			num=num*2+1,num%=mod;
		}else{
			ans++;
			ans+=num,ans%=mod;
			num=num*2+1,num%=mod;
		}
	}
	cout << ans;
}

星期六:

pta 森森旅游,一道很新的题                         pta链接

思路:需要跑两遍dij,一遍是从1用现金到各点,一遍是从n用旅游金到各点,这样在 i 点换钱所需的现金就是 dis1【i】+disn【i】/ a【i】(除法向上取整,然后把结果丢 multiset 里,在询问时动态维护

代码如下:( 不知哪里扣了1分

const int N=1e5+10,M=210;
const int mod=1e9+7;
ll n;
int m,q;
vector<PII>vec[N],ved[N];
ll disc[N],disd[N],a[N];
bool vi[N];
multiset<ll>ms;
void dij1(int s){
	priority_queue<PII,vector<PII>,greater<PII>>pq;
	memset(disc,0x3f,sizeof disc);
	disc[s]=0;
	pq.push({0,s});
	while(!pq.empty()){
		auto [d,t]=pq.top(); pq.pop();
		if(vi[t]) continue;
		vi[t]=1;
		for(auto [v,w]:vec[t]){
			if(disc[w]<=d+v) continue;
			disc[w]=d+v;
			pq.push({disc[w],w});
		}
	}
}
void dij2(int s){
	priority_queue<PII,vector<PII>,greater<PII>>pq;
	memset(disd,0x3f,sizeof disd);
	disd[s]=0;
	pq.push({0,s});
	while(!pq.empty()){
		auto [d,t]=pq.top(); pq.pop();
		if(vi[t]) continue;
		vi[t]=1;
		for(auto [v,w]:ved[t]){
			if(disd[w]<=d+v) continue;
			disd[w]=d+v;
			pq.push({disd[w],w});
		}
	}
}
void solve(){
	cin >> n >> m >> q;
	while(m--){
		int u,v,c,d; cin >> u >> v >> c >>d;
		vec[u].push_back({c,v});
		ved[v].push_back({d,u});             //反向建图
	}
	dij1(1);
	for(int i=1;i<=n;i++) vi[i]=0;
	dij2(n);
	for(int i=1;i<=n;i++){
		cin >> a[i];
		ll c=disc[i]+disd[i]/a[i];
		if(disd[i]%a[i]) c++;                //向上取整
		ms.insert(c);
	}
	while(q--){
		int x,b; cin >> x >> b;
		ll c=disc[x]+disd[x]/a[x];
		if(disd[x]%a[x]) c++;
		ms.erase(ms.find(c));                //删除原来的,再把新的插进去
		a[x]=b;
		c=disc[x]+disd[x]/a[x];
		if(disd[x]%a[x]) c++;
		ms.insert(c);
		cout << *(ms.begin()) << "\n";       //输出最小值
	}
}

天梯赛237,国二,洋洋得意

又可以爽刷dp,构造咯

atcoder abc350 C 给我绕晕了                            atc传送门

思路:模拟交换即可,只是要注意 思路保持清晰

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int a[N],c[N];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		c[a[i]]=i;               //c储存各个数的下标位置
	}
	int num=0;
	vector<PII>ve;
	for(int i=1;i<=n;i++){
		if(a[i]==i) continue;
		num++;
		int idx=c[i];           //i数下标为idx
		ve.push_back({i,idx});
		swap(a[i],a[idx]);
		c[a[idx]]=idx;          //交换后更新下标信息
	}
	cout << num << "\n";
	for(auto [x,y]:ve) cout << x << " " << y << "\n";
}

周日:

组队赛训练,又开始坐牢

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值