2024ICPC网络赛前总复习 2024.2.29复盘 30-40页

https://www.luogu.com.cn/problem/CF1934B

此题有完全背包写法 不再赘述
意识到我们不可能用3个1去换一个3 也不可能用2个3换一个6.。一次类推开几个for循环

void solve() {
	int lte = 1e9;
	cin >> n;
	for (int i = 0; i <= 2; i++) {
		for (int j = 0; j <= 1; j++) {
			for (int h = 0; h <= 4; h++) {
				for (int s = 0; s <= 2; s++) {
					int now = i + j * 3 + h * 6 + s * 10;			
					int need = (n - now) % 15;	
					if (need != 0||n-now<0)continue;
					else {	//cout<<now<<endl;
						int ans = i + j + h + s + (n - now) / 15;
						lte = min(lte, ans);
					}
				}
			}
		}
	}
  cout<<lte<<endl;
}

https://www.luogu.com.cn/problem/CF1934C

考了一个曼哈顿距离 猜出其中一个地雷坐标

我们先查 1,1 查到了一条直线再查这两端点就行 这样就可以规避掉第二个点的影响了
也可以先查1 1 然后查n m 得到两条直线 后面查n 1 其中一个交点肯定是答案
很好的一道题

https://www.luogu.com.cn/problem/CF1873F

复习到了money trees 那就再讲清楚两种做法吧
第一种是二分长度做法 第二种是双指针做法

第一种二分长度 这种需要倒着for循环看最大延申长度 得到了最大长度然后使用前缀和
判断是否小于题目限制 后看是否满足二分的mid

bool check(int changdu)
{
	for(int i=1;i<=n-changdu+1;i++)
	{
		if(sum[i+changdu-1]-sum[i-1]<=k)
		{
			if(c[i]-i+1>=changdu)return 1;
		}
	}
	return 0;
}

第二种双指针 使用的最多的技巧
需要定义一个左指针l 然后不断延申出去 一旦大于题目限制就l++

	int l=1;
	int r;
	for(int i=1;i<=n;i++)
	{
		if(h[i-1]%h[i]){
			l=i;
		}//题目另外的限制条件 
		while(sum[i]-sum[l-1]>k)
		{
			l++;
		}
		ans=max(ans,i-l+1);
	}

https://www.luogu.com.cn/problem/CF1873H

tarjan抓捕问题 这个题讲了很多次了 深深印在我的脑子里 他很重要
我记得无向图tarjan我记得两个写法 一个是我的板子
还有就是说如果你碰到了更小的那个度数 此时那个点在stk里面 那就可以判断了

好像是这样 我有点忘记了 注意不是instk里

不过我这个板子的fa^1就已经够用了
然后注意多测情况 首先明白这是一个基环树 让我们复习一下他的性质
任意一个点有两条路径(就是完全是一个环) 并且和是n 有且只有一个环
对于环之外的树按照若干棵别的树做处理 再与环一起计算 这个后面会有题的 我没记错的话

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int maxn=400010;
int n;
int s,t;//s抓t
int low[maxn],dfn[maxn],tot;
int h[maxn],e[maxn],ne[maxn],idx;
int stk[maxn],scc[maxn],siz[maxn],top,cnt;
int vis[maxn];int instk[maxn];
int cntt[maxn][4];
int LTE[maxn];
vector<int>v[maxn];
void cclear()
{
	top=0;cnt=0;idx=0;tot=0;
	for(int i=0;i<=n;i++)h[i]=-1;
//	cout<<h[2]<<endl;
	for(int i=1;i<=n;i++)
	{   v[i].clear();
		LTE[i]=siz[i]=0;
		instk[i]=ne[i]=e[i]=stk[i]=scc[i]=dfn[i]=low[i]=0;
		cntt[i][1]=cntt[i][2]=0;
		//wc wotm 服了 我在这里又初始h为0了 卧槽 卧槽 调一个小时
	}	
}
void add(int a,int b)
{ //cout<<a<<b<<" "<<h[a]<<"----------";
	e[idx]=b; 
	ne[idx]=h[a];
//	cout<<ne[idx]<<h[a]<<endl;
	h[a]=idx++;	
}//不能写正常的tarjan 因为这是无向图 
//下面这个写法可以应对所有情况 但是instk不行
void tarjan(int x,int fa)
{
	dfn[x]=low[x]=++tot;
	stk[++top]=x;
//	instk[x]=1;
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		//	cout<<"x: "<<x<<" J: "<<j<<endl;
		if(!dfn[j]){
			tarjan(j,i);
			low[x]=min(low[x],low[j]);
		}
		else if(i!=(fa^1)){
			low[x]=min(low[x],dfn[j]);
		}		
	}
	if(dfn[x]==low[x])
	{
		++cnt;
		int y;
		do{
			
			y=stk[top--];
			//	instk[y]=0;
			v[cnt].push_back(y);
			//		cout<<cnt<<"  "<<y<<endl;
			//不能不push因为 单个也会被tarjan视为一个强联通分量
			scc[y]=cnt;
			++siz[cnt];
		}while(y!=x);
	}
}
void bfs(int x)
{   queue<int>q; 	
	int name;
	for(int i=1;i<=n;i++)vis[i]=0;
	if(x==s)name=1;
	else name=2;
	vis[x]=1;
	q.push(x);
	while(!q.empty())
	{
		int u=q.front();
		//	cout<<"母亲 "<<u<<endl;
		q.pop();
		for(int i=h[u];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(!vis[j])
			{
				vis[j]=1;
				cntt[j][name]=cntt[u][name]+1;
				q.push(j);
			}
		}
	}	
}
void solve()
{
	cin>>n;
	cin>>s>>t;
	cclear();
	for(int i=1;i<=n;i++)
	{
		int a,b;
		cin>>a>>b;
		add(a,b);
		add(b,a);
	}	
	bfs(s);
	bfs(t);
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])tarjan(i,0);
	}
	for(int i=1;i<=cnt;i++)
	{
		if(siz[i]>=3)
		{
			for(auto hs:v[i])
			{
				LTE[hs]=1;
			}
		}	
	}
	if(s==t){
		cout<<"NO"<<endl;return ;
	}
	
	if(LTE[t]){
		cout<<"YES"<<endl;return ;
	}
	for(int i=1;i<=n;i++)
	{
		if(LTE[i])
		{
			if(cntt[i][1]>cntt[i][2])
			{
				cout<<"YES"<<endl;
				return;
			}
		}
	}
	cout<<"NO"<<endl;
	return ;
}
signed main()
{
	int tt;
	cin>>tt;
	while(tt--)solve();
	return 0;
}

https://www.luogu.com.cn/problem/CF1850F

首先题目翻译有点傻逼

应该是每一秒对于的第i只青蛙跳ai 一眼就看出来了这是一个倍数有关的题目
很明显 暴力的话就是一个不是随机数的埃氏筛
这样会被全是1的毒瘤数据卡死了 导致n方 所以
我么只需要修改成整体埃氏筛即可

	for(int  i=1;i<=n;i++)
	{ 
		//if(ma[i]==0)continue;
		for(int j=0;j<=n;j=j+i)
		{
			cnt[j]+=ma[i];
		}
	}
把j+=a[i]改成i就行了 这样就不会t 最终for扫一遍就行了 

https://www.luogu.com.cn/problem/CF1850H

想了一会 没写出来 看了代码才发现是图论 当初自己写代码意识到了
。。。。写个递归就行了
非常好的一道题 如果某一时刻访问过却和以前冲突的 就是错的 直接输出

#include<bits/stdc++.h>
#define int long long 
using namespace std;
struct node{
	int v;
	int w;
};
vector<node>e[200005];
int n;int m;
int a[200005];
int dis[200005];
map<int,int>mm;
bool flag=0;
void dfs(int  ma,int q)
{
	if(dis[ma])return ;//来到同一个点 如果是反向边没事 怕就怕重复
	if(flag)return ;
	//但是你q=0开始 dis就不会return 如果有那种重复情况
	//但是特判这种情况 就很无语 因为你有反向边 一定会重复走到0
	// 不过也有办法用那个from^1? 我不会
	dis[ma]=q;
	for(auto i:e[ma])
	{
		if(!dis[i.v])
			//&&mm[dis[i.v]]<=1)
		{
			dfs(i.v,dis[ma]+i.w);
		}
		else {
			if(dis[i.v]!=dis[ma]+i.w){
				flag=1;
				return ;
			}		
		}
		if(flag)return ;
	}
}
void solve()
{
	cin>>n;cin>>m;
	mm.clear();
	flag=0;
	for(int i=1;i<=m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		e[a].push_back((node){b,c});
		e[b].push_back((node){a,-c});
	}
	dis[1]=0;
	for(int i=1;i<=n;i++)
	{
		if(!dis[i]){
			dfs(i,1e15);
		}
		if(flag)break;
	}
	if(flag)cout<<"NO"<<endl;
	else cout<<"YES"<<endl;
	for(int i=1;i<=n;i++)
	{
		dis[i]=0;
		e[i].clear();
	}
	return ;
}

https://www.luogu.com.cn/problem/CF1829F


雪花 说下我的口糊错误做法 我错在认为度数最高的一个点一定是根节点 这是错的

谁保证了那个纸条一定是3,4这样很小的 所以我们要抓住一个点

就是度数为1的那个叶子 知道了叶子的总数 然后也可以知道第二层的树 于是就做出来了

放我以前 直接秒了

void solve()
{
	cin>>n;cin>>m;
	set<int>s;
	int ans=0;
	for(int i=1;i<=m;i++)
	{
		cin>>a>>b;
	  v[a].push_back(b);
		cnt[a]++;
		v[b].push_back(a);
		cnt[b]++;
	}
	for(int i=1;i<=n;i++)
	{
		if(cnt[i]==1)
		{
		ans++;	s.insert(v[i][0]);
		}
	}
	int x=ans/s.size();
	cout<<s.size()<<" "<<x<<endl;
	for(int i=1;i<=n;i++)
	{
		cnt[i]=0;
		v[i].clear();
	}

	
}

https://www.luogu.com.cn/problem/CF1829H

DP 好题
首先一定要观察数据范围 ai的取值仅仅到63 还有k也只是6而已

如果数据打了这题就没法做了

所以我们完全可以第二维暴力存数值就行了 然后等会把第二维单独
抽出来看满足k就行了

开一个二维dp
对于任何一个ai来说 我可以继承上一个 也可以不继承

不继承那就是从他这边重新开 然后这个也可以继承之前的数值

   f[i][a[i]] = 1;
   f[i][j] = (1ll * f[i][j] + f[i - 1][j]) % mod;
   f[i][j & a[i]] = (1ll * f[i][j & a[i]] + f[i - 1][j]) % mod;
	cin>>n;cin>>k;
	int ans=0;
	for(int i=1;i<=n;i++){cin>>a[i];}
	for(int i=1;i<=n;i++)
	{
		dp[i][a[i]]=1;
		for(int j=0;j<=63;j++)
		{
			dp[i][j]=(dp[i-1][j]+dp[i][j])%mod;
//		    dp[i][j&a[i]]=dp[i-1][j&a[i]]+dp[i][j&a[i]]%mod;
			 dp[i][j&a[i]]=(dp[i-1][j]+dp[i][j&a[i]])%mod;
		}
	}
	for(int i=0;i<=63;i++)
	{ int cnt=0;
		for(int j=0;j<=6;j++)
		{
			if(i>>j&1){
				cnt++;
			}
		}
		if(cnt==k){
			ans+=dp[n][i]%mod;
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=63;j++)
		{
			dp[i][j]=0;
			
		}
	}
	cout<<ans%mod<<endl;
	

然后本题结束
https://www.luogu.com.cn/problem/CF1807E

一道可以拿来练手的交互二分 不难

https://www.luogu.com.cn/problem/CF1807F

很好的一道模拟题目 开dx dy模拟前进方向

    if(s[0]=='D')dx=1;
	else dx=-1;
	if(s[1]=='L')dy=-1;
	else dy=1;
	x+=dx;
	y+=dy
然后碰壁就是 x是否会是1 y是否会是m这样判断 
cin>>n;
	cin>>m;
	cin>>x>>y;
	cin>>tx>>ty;
	string s;
	int dx,dy;
	cin>>s;
	if(s[0]=='D')dx=1;
	else dx=-1;
	if(s[1]=='L')dy=-1;
	else dy=1;
	int lim=4*n*m;
	int cnt=0;
	while(lim--)
	{ 
		bool flag=0;
	//	cout<<x<<" "<<y<<" "<<dx<<" "<<dy<<endl;
		if(x==tx&&y==ty){
			cout<<cnt<<endl;
			return ;
		}
		//shang
		if(x==1&&dx==-1)
		{
			dx=1;//cnt++;
			flag=1;
		}//xia
	if(x==n&&dx==1)
	{	flag=1;
		dx=-1;//cnt++;	
	}//zuo 
		if(y==1&&dy==-1)
	{	flag=1;
		dy=1;//cnt++;	
	}//you
		if(y==m&&dy==1)
	{	flag=1;
		dy=-1;//cnt++;	
	}
		if(flag)cnt++;
		x+=dx;
		y+=dy;		
	}
	 cout<<-1<<endl;

https://www.luogu.com.cn/problem/P1714

首先要明白我们是求一个m区间之内的连续和最大值 所以m就是一个窗口
然后求这个最大值 我么我们可以想到既然连续 前缀和很重要 于是可以单调队列一个前缀和最小值
那么我们就可以得到sumi-sum队头就是一个答案 然后不断刷新保存ans 最大就可以了

	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]+a[i];
	}
//	cout<<"ss"<<endl;
	q[0]=0;
	ans=sum[1];
	sum[0]=0;
	for(int i=1;i<=n;i++)
    {  
	//	cout<<sum[i]<<endl;
		if(h<=t&&q[h]<i-k)h++;	
		ans=max(ans,sum[i]-sum[q[h]]);
		//不是 sum[h]
		while(h<=t&&sum[i]<=sum[q[t]])
		{
			t--;
		}
		q[++t]=i;	
	}	

https://www.luogu.com.cn/problem/P1638


这题太典了 很明显一道单调队列
不过有点麻烦
队列保存的是一段队头不会重复的数字 队头一旦重复 就直接pop掉

那么对于此时如果达到了m 那么可以开始统计答案 a就是队头 那么 b就是 此时的i

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int range = 2e5 + 5;
int n;
int m;
int a[1000005];
int q[1000005];
set<int>s;
void solve() {
	map<int, int>ma;
	ma.clear();
	int lte=1e9;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i];
	int h = 1;
	int t = 0;
	int  x=1e9, b;
	for (int i = 1; i <= n; i++) {
		ma[a[i]]++;
		//不能用if 否则就减一次 要找到只有一个的时候
		while (h <= t && ma[a[q[h]]] > 1) {
			ma[a[q[h]]]--;	
			h++	;
		}
		q[++t] = i;
		if (ma.size() == m) {
			int ans=i-q[h];
			if(ans<=lte)
			{
				if(h<x&&ans==lte)
				{x = h; 
					lte=ans;
					b = i;}
				else if(ans<lte)
				{
					x = h; 
					lte=ans;
					b = i;
				}
			} 
			
		}
	}
	cout<<x<<" "<<b;
}
signed main() {
	ios::sync_with_stdio();
	cin.tie(0);
	cout.tie(0);
	solve();
	return 0;
}

https://www.luogu.com.cn/problem/CF1760G

这道题隔了好久再思考
思考了几分钟 少思考了一点东西 就是对b也要进行深搜 我一开始思考少了 你比如产生
断点的话 那a也搜不到b去
所以需要搜两次
然后答案很明显就是 某一时刻 a的map标记了 b搜的时候发现此时函数自身带的变量sum
ma[sum]有值的 那就有答案了

#include <bits/stdc++.h>
//hs lte
#define int long long
#define endl '\n'
using namespace std;
const int mod = 1e9 + 7;
int n;
int jc[200005];
struct node {
	int v, w;
};
int  a, b;
map<int, bool>ma;
bool flag = 0;
//队列清空
vector<node>e[100005];
int qpow(int a, int n) {
	//a是模数 或者说的平方数
	int ans = 1;
	while (n) {
		if (n & 1)	ans = ans * a % mod;
		a = a * a % mod;
		n >>= 1;
	}
	return ans % mod;
}
int inv(int x) {
	return qpow(x, mod - 2);
}
int C(int x, int m) {
//	for (ll i = 1; i <= m; i++) {
//		c[i] = c[i - 1] * (m - i + 1) % p * qpow(i, p - 2) % p;
//	}
	int g = (jc[x] * inv(jc[x - m]) % mod) * inv(jc[m]);
//	cout<<endl<<"g  "<<g<<endl;
	return g % mod;
}
void adfs(int st, int fa, int sum) {
	for (auto i : e[st]) {
		if (i.v == fa || i.v == b)continue;
		ma[sum ^ i.w] = 1;
		adfs(i.v, st, sum ^ i.w);
	}
	return ;
}
void bdfs(int st, int fa, int sum) {
	if (flag)return ;
	for (auto i : e[st]) {
		if (i.v == fa)continue;
		if (ma[sum ^ i.w] == 1) {
			flag = 1;
			return ;
		}
		bdfs(i.v, st, sum ^ i.w);
		//日了狗的 这里写了个adfs我草草草草
	}
	return ;
}
void solve(int t) {
	int x, y, z;
	cin >> n >> a >> b;
	for(int i=1;i<=n;i++)
	{
		e[i].clear();
	}
	ma.clear();
	flag=0;
	for (int i = 1; i <= n - 1; i++) {
		cin >> x >> y >> z;
		e[x].push_back((node) {
			y, z
		});
		e[y].push_back((node) {
			x, z
		});
	}
	ma[0]=1;
	adfs(a, 0, 0);
	bdfs(b, 0, 0);
	//直接tp
	if (flag) {
		cout << "YES " << endl;
		return ;
	} else {
		cout << "NO" << endl;
		return ;
	}
}

https://www.luogu.com.cn/problem/CF1722G


再回头只觉得简单 空出3个位置 统计好sum 别的就随便了


void solve()
{
	cin>>n;
	int sum=0;
	for(int i=1;i<=n-3;i++)
	{cout<<i<<" ";
		sum^=i;
	}
	int a=1<<18;
	int b=1<<19;
	int x=sum^a^b;
	cout<<a<<" "<<b<<" "<<x<<endl;
	
}

当时理解半天的题目呢
https://www.luogu.com.cn/problem/CF1703E

矩阵旋转问题 要学会公式怎么推就行了 别的就没什么了


只需要知道90度就行的 180不就90+90嘛
公式

https://www.luogu.com.cn/problem/P1130

一道橙dp 还挺有意思的 不过逻辑就那么多 没啥说的
就是题目不要理解错了 然后 注意+a[j][i]因为第i个步骤 适合新生练手


int n;int m;
int a[2005][2005];
int f[2560][2005];
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n;j++)
			cin>>a[i][j];
	}
	for(int i=1;i<=n;i++)
	{    
		f[0][i-1]=f[m][i-1];
		for(int j=1;j<=m;j++)
		{
			f[j][i]=min(f[j-1][i-1],f[j][i-1])+a[j][i];
		}	
	}
//	for(LL i=1;i<=n;i++){//循环n个步骤
//		dp[0][i-1]=dp[m][i-1];//本题重点就在这里了!上一步的第零位,其实就是上一位的最后一位,因为最后一个小组是可以更换到1的
//		for(LL j=1;j<=m;j++)//循环m个人,每一个步骤都有m个人可以完成,挨个儿决策
//			dp[j][i]=min(dp[j-1][i-1],dp[j][i-1])+gay[j][i];//第i个人做第j步,可以由它的第j-1个步骤的第i-1个人或者第i个人转移过来
//	}
	int mini=1e8;
	for(int i=1;i<=m;i++)
	{
		mini=min(mini,f[i][n]);
	}
	cout<<mini;
}

https://www.luogu.com.cn/problem/P8742

砝码称重 非常好一道题

可以两个加起来称某一个东西 也可以相减
那么代码书写 就是要前后扫一次dp 总共两次dp 我喜欢称这个为扫

然后 注意相减的那种要写成 dp[j]|=dp[j+a[i]]; 为什么呢
自己思考吧


int dp[range];
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
	dp[0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=sum;j>=a[i];j--)
		{
			dp[j]|=dp[j-a[i]];
		}
	}//xiangjiade 
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=sum-a[i];j++)
		{ 
			//不能用两者相加造出来的砝码当作现有的
			dp[j]|=dp[j+a[i]];
		}
	}
	int ans=0;
	for(int i=1;i<=sum;i++){
		if(dp[i])ans++;
	}
	cout<<ans;
	//
}

https://www.luogu.com.cn/problem/P1569


有点像刚才上面单调队列的前缀和问题 很像那个最大子序列

一般涉及到连续又要最大啥的 很难不dp
然后 这题 比如说 我们调了一个i就可以往前倒着找 看看最多构成几组 但是大于0的情况下
其实就是最大子序列 我都想删掉这个题了


void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];
		if(sum[i]>=0)f[i]=1;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)
		{
			if(f[j]>0&&sum[i]-sum[j]>=0)
			{
				f[i]=max(f[i],f[j]+1);
			}
			
		}
	}
	if(f[n]==0)cout<<"Impossible";
	else cout<<f[n];	
}

https://www.luogu.com.cn/problem/P1719

最大加权矩阵
非常好一道题 可以暴力用二位前缀和做 竟然不超时 我会讲复杂度分析
但是最关键还是要学会矩阵压缩
首先对于代码

void check() {
	memset(dp, 0, sizeof dp);
	for (int i = 1; i <= n; i++) {
		dp[i] = max(dp[i - 1] + temp[i], dp[i]);
		ans = max(ans, dp[i]);
	}
}
void solve() {
	for (int i = 1; i <= n; i++) {
		memset(temp, 0, sizeof temp);
		for (int j = i; j <= n; j++) {
			for (int k = 1; k <= n; k++) {
				temp[k] += a[j][k];
			}
			check();
		}
	}
}

对于solve函数 而言 注意到有三层循环
第一次 我们 会从第一行一直取到最后一行
第二次则是从第二行取到最后一行 每一次i变了 temp清空
然后temp是记载列的 不是行 注意
0 –2 –7 0
9 2 –6 2
-4 1 –4 1
-1 8 0 –2
比如 0+9 -2+2 这样的 列相加
然后我们 每次一个行的列统计完都会check一次 比如 一开始的
0 -2 -7 0
check函数里 dp[i] = max(dp[i - 1] + temp[i], dp[i]);
然后就是dpi-1如果说是小于0的 我们从第一列开始统计嘛 第一列小于0那dp1就是0
如果dp2+dp1反而小于0的 那第二列断开 默认dp2=0 dp1计入ans里去了
很灵活的

很好的模板

再来讲下On^4的做法 但是此题不会超时 数据小

 for(int x1=1;x1<=n;x1++){
    	for(int y1=1;y1<=n;y1++){
    		for(int x2=1;x2<=n;x2++){
    			for(int y2=1;y2<=n;y2++){
    				if(x2<x1 || y2<y1) continue;//如果左上角比右下角还要大,就不用求了,下一个
  	mx=max(mx,sum[x2][y2]+sum[x1-1][y1-1]-sum[x2][y1-1]-sum[x1-1][y2]);//求最大值
    			}
    		}
    	}
    }

分析这个复杂度才是我想说明的

等于这个

对于∑n-y1+1拆开就是


于是就是两个相乘 下面底数是4 肯定超不了的 这个复杂度计算很有意思

https://www.luogu.com.cn/problem/P1877


首先做dp一定要观察数据范围

优先考虑存数组表示 于是 转移就简单多了

#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
using namespace std;
const int range=2e5+5;
int n;
int beg;
int maxf;
int c[200005];
int dp[55][1005];
void solve()
{
	//你可以看到我那个dp00=1
	///不该这么写的 我老是忘记	dp[i][j]|=dp[i-1][j+c[i]]
	//这个就是对是否取到j=0的情况呀 我老是忘
	//老是以为这里j不是0 以为是c【i】
	//
	//切记
	//切记 我老是这么认为
	cin>>n;
	cin>>beg>>maxf;
	for(int i=1;i<=n;i++)cin>>c[i];
	dp[0][beg]=1;
	for(int i=1;i<=n;i++)
	{
		bool flag=0;
		for(int j=0;j<=maxf;j++)
		{	
			if(j>=c[i])
			{
				dp[i][j]|=dp[i-1][j-c[i]];//,flag=1;
			}
			if(j+c[i]<=maxf){
				dp[i][j]|=dp[i-1][j+c[i]];//,flag=1;
			}
			//我这样思考还是周到 不是j+c小于就更新flag这不对
			//我下面那个样例就说明了 
			if(dp[i][j])flag=1; 
		}
		
		if(flag==0){
			cout<<-1;
			return ;
		}
	}
	for(int i=maxf;i>=0;i--)
	{
		if(dp[n][i]){
			
			cout<<i;
			return ;
		}
	}
}

https://www.luogu.com.cn/problem/P9325

不可以考虑暴力做法
要体会到区间合并的概念
又大区间从小区间得到
观察以下 不难发现 5可以由3的来
4可以由2的来 于是此题做出来了

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int range = 2e5 + 5;
int n;
int a[200005];
int h[range];
int b[range];
void solve() {
	
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> h[i];
	cout<<"0"<<" ";
	for (int len = 2; len <= n; len++) {
		int ans = 1e9;
		if (len % 2 == 1) {
			for (int l = 1; l + len - 1 <= n; l++) {
				int r = l + len - 1;
				a[l] = a[l + 1] + abs(h[r] - h[l]);
				ans = min(a[l], ans);
			}	
		} else {
			for (int l = 1; l + len - 1 <= n; l++) {
				int r = l + len - 1;
				b[l] = b[l + 1] + abs(h[l] - h[r]);
				ans = min(b[l], ans);
			}
		}
		cout<<ans<<" ";
	}
}

https://www.luogu.com.cn/problem/P2858

想了一下 就写出来了
观察好数据范围 发现完全可以用数组直接记录
于是考虑n n记录就行
然后 发现第t天 总共选了 多少个有两种可能

	for(register int i=1;i<=n;i++)
		for(register int j=0;j<=i;j++)
		{
			register int l=i-j;//推出右边取了多少个
			dp[i][j]=max(dp[i-1][j]+in[n-l+1]*i,dp[i-1][j-1]+in[j]*i);//状态转移
            //n-l+1就是从右边数第l个在in数组中的下标
		}

翻到一个题解
dp[i][j]表示已经取了i个数,左边取了j个数的最优解
和我一样的思路 不过我以前做的话的思路是

    for(int len=2;len<=n;len++)
	{
		for(int l=1;l+len-1<=n;l++)
		{
			int r=l+len-1;	 
		dp[l][r]=max(dp[l+1][r]+v[l]*(n-len+1),dp[l][r-1]+v[r]*(n-len+1));

就是倒着思考了 反而是l或者r是最后才取到的意思 比如说2 - 7 我假设2是现在才取到 或者7现在才取到 我现在的思路反而不是这样的

不过没啥 没啥大区别 dp方程开对了就行
https://www.luogu.com.cn/problem/P3146


这个题不难想到区间dp 数据这么小
if(dp[l][k]==dp[k+1][r]&&>0)
对于l r 中间的值有大的尽量更新 我觉得是没后效性的 因为dp[l][r]
限定了只有l-r才有这个值 更别的也不冲突

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int range = 2e5 + 5;
int n;
int a[200005];
int dp[2004][2004];
void solve() {
	cin >> n;
	int ans = -1;
	for (int i = 1; i <= n; i++)cin >> a[i],ans=max(a[i],ans);
	for (int i = 1; i <= n; i++) dp[i][i] = a[i];
	for (int len = 2; len <= n; len++) {
		for (int l = 1; l + len - 1 <= n; l++) {
			int r = l + len - 1;
			if (len == 2) {
				for (int k = l; k < r; k++) {
					if (dp[l][k] == dp[k + 1][r])
						dp[l][r] = max(dp[l][r], dp[l][k] + 1);
					ans = max(ans, dp[l][r]);
				}
			} else {
				for (int k = l; k < r; k++) {
					if (dp[l][k] == dp[k + 1][r]&&dp[l][k]!=0) {
						dp[l][r] = max(dp[l][r], dp[l][k] + 1);
						ans = max(ans, dp[l][r]);
					}
				}
			}
		}
	}
	cout<<ans<<endl;
}

https://www.luogu.com.cn/problem/P3205

首先这个题 给我的赶紧很熟悉
暑假好像做过类似的 不过不知道是不是区间dp
然后 这个题一定要搞清楚进来的顺序 以及大小
我自己打了一遍草稿才知道的

#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
using namespace std;
const int range=2e5+5;
const int mod=19650827;
int n;
int f[2005][2005][2];
int a[200005];
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)f[i][i][0]=1;
	for(int len=2;len<=n;len++)
	{
		for(int i=1;i+len-1<=n;i++)
		{
			int j=i+len-1;
			if(a[i]<a[i+1])
			{
				f[i][j][0]+=f[i+1][j][0];
			}
			if(a[i]<a[j]){
				f[i][j][0]+=f[i+1][j][1];
			}
			if(a[j]>a[j-1])
			{
				f[i][j][1]+=f[i][j-1][1];
				
			}
			if(a[j]>a[i]){
				f[i][j][1]+=f[i][j-1][0];
				//f[i][j-1][0] j在最右 i刚好弄进来 然后比不过 去了右边
			}
			f[i][j][0]%=mod;
			f[i][j][1] %=mod; 
			
		}
	}
	//第i个人从左边来,他既然从左边来,
	//要么第i+1人从左边来且h[i+1]>h[i];
	//要么第j个人从右边来且h[j]>h[i];
	
	//第j个人从右边来,他既然从右边来,
	//要么第j-1人从右边来且h[j]>h[j-1];
	//要么第i个人从左边来且h[j]>h[i];这样你i就该去右边
	
	int hslte=(f[1][n][0]+f[1][n][1])%mod;
	cout<<hslte;
	
}
posted @ 2024-09-09 13:56  LteShuai  阅读(69)  评论(0)    收藏  举报