最近做题小结-含没有总结的牛客视频以及各个平台题目-已经解决

前言

来到产业园第一次写题 最近做的题目很杂 涉及很多个平台 vj loj cf 牛客 lg 然后等会一个个找吧 按照时间顺序写吧
一直拖着 没写题解 最近状态一般 想回家了

第一个题

这个题 我没写出来 因为我不会处理第二个平台的问题 谁能想到只需要记好第一个就行了呢
利用一个while循环记录 很好的trick

for(int i=1;i<=n;i++)
	{
		while(x[r]-x[i]<=k&&r<=n)r++;
		f[i]=r-i;
	}
   
	maxn=max(maxn,f[i]+hsum[f[i]+i]);
//这种双指针不仅仅配合sum
//我不知道怎么处理两条 而且也没想到 使用后缀最大值去记录答案 
//至于你说的这个while循环 我是想了的 可是我就是没想到怎么处理第二条
//哪知道第二条只需要记录后缀即可 反正一定不相交
//可惜可惜
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define debug cout<<endl<<"----------"<<endl;
using namespace std;
const int range=3e5+10;
int n;
int k;
//这种双指针不仅仅配合sum
//我不知道怎么处理两条 而且也没想到 使用后缀最大值去记录答案 
//至于你说的这个while循环 我是想了的 可是我就是没想到怎么处理第二条
//哪知道第二条只需要记录后缀即可 反正一定不相交
//可惜可惜
//int a[range];
int x[range];
int y[range];
int f[range];
int hsum[range];
void init()
{
	for(int i=1;i<=n;i++)
	{
		x[i]=y[i]=f[i]=hsum[i]=0;
	}
}
void solve()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)cin>>x[i];
	for(int i=1;i<=n;i++)cin>>y[i];
	sort(x+1,x+1+n);
	int r=1;

	for(int i=1;i<=n;i++)
	{
		while(x[r]-x[i]<=k&&r<=n)r++;
		f[i]=r-i;
	}
	for(int i=n;i>=1;i--)
		hsum[i]=max(hsum[i+1],f[i]);
	
	int maxn=0;
	for(int i=1;i<=n;i++)
		maxn=max(maxn,f[i]+hsum[f[i]+i]);
	cout<<maxn<<endl;
	init();
}

第二个题


int dp[range][range];
void solve()
{			
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	for(int i=1;i<=n;i++)
	{
		a[i+n]=a[i];
	}
	int maxn=0;
	for(int len=3;len<=n+1;len++)
	{
		for(int l=1;l+len-1<=2*n;l++)
		{
			int r=l+len-1;
			for(int k=l+1;k<r;k++)
			{
				dp[l][r]=max(dp[l][r],dp[l][k]+dp[k][r]+a[l]*a[k]*a[r]);
				maxn=max(dp[l][r],maxn);	
			}
		}
	}	
	cout<<maxn<<endl;
	return ;
}

7.15我在看牛客的dp 顺手写的

第三个

听雨巨讲的 这个题
做了蛮久的 对于田忌来说 他只有两个选择 要么拿最好的马
要么拿最差的马 中间的马是没用的
对于
田鸡 2 3
齐王 1 3
并不是说最大打不赢就一定上早少的 不然上面那个就是平的 所以我们其实可以观察到
这个状态方程式只跟首尾有关
dp[l][r]=max(dp[l+1][r],dp[l][r-1]分别表示选l和r的情况
所以这题实际上需要对田鸡从大到小排序 对齐王从小到大排序 这样才满足我们的想法
我测试了齐王从小到大也行 他顺序没用影响 这个排序因人而异
然后就可以做出来了

//#include <bits/stdc++.h>
//#define int long long
//#define endl '\n'
//#define debug cout<<endl<<"----------"<<endl;
//using namespace std;
//const int range = 4e3 + 10;
//int n;
//int a[range];
//int tian[range];
//int qi[range];
//int dp[range][range];
//int calc(int x,int y)
//{
//	if(tian[x]>qi[y])return 200;
//	else if(tian[x]==qi[y])return 0;
//	else return -200;
//}
//void solve() {
//	cin >> n;
//	for (int i = 1; i <= n; i++)cin >> tian[i];
//	for (int i = 1; i <= n; i++)cin >> qi[i];
//	sort(tian + 1, tian + 1 + n);
//	sort(qi + 1, qi + 1 + n);
//	//92 83 71
//	//85 87 74	
//	for (int len = 1; 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]+calc(l,len),dp[l][r-1]+calc(r,len));		
////			debug
////			cout<<dp[l][r]<<" "<<l<<" "<<r<<endl;
////			cout<<calc(l,len)<<" "<<calc(r,len)<<endl;
////牛魔 洛谷竟然能过			
//		}
//		//r-l+1=n-k+1
//	//k=n-len+1
//	}
//	cout<<dp[1][n];
//}
//signed main() {
//	ios::sync_with_stdio();
//	cin.tie(0);
//	cout.tie(0);
//	solve();
//	return 0;
//
//
//}
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define debug cout<<endl<<"----------"<<endl;
using namespace std;
const int range = 4e3 + 10;
int n;
int a[range];
int tian[range];
int qi[range];
int dp[range][range];
int calc(int x,int y)
{
	if(tian[x]>qi[y])return 200;
	else if(tian[x]==qi[y])return 0;
	else return -200;
}
void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> tian[i];
	for (int i = 1; i <= n; i++)cin >> qi[i];
	for(int i=1;i<=3100;i++)
	{
		for(int j=1;j<=3090;j++)dp[i][j]=-1e9;
	}
	sort(tian + 1, tian + 1 + n, greater<int>());
	sort(qi + 1, qi + 1 + n);
	for(int i=1;i<=n;i++)
	{
		dp[i][i]=calc(i,1);
	}
	// 10 3 3 2 2 1 
	// 10 9 9 8 7 6  

	for (int len = 2; len <= n; len++) {
		for (int l = 1; l + len - 1 <= n; l++) {
			int r=l+len-1;	
		//	debug
		//	cout<<dp[l][r]<<endl;
dp[l][r]=max(dp[l][r],max(dp[l+1][r]+calc(l,len),dp[l][r-1]+calc(r,len)));	
		//	cout<<dp[l][r]<<" "<<l<<" "<<r<<endl;
		//	cout<<calc(l,len)<<" "<<calc(r,len)<<endl;
		//	cout<<len<<" ";
		}
		//r-l+1=n-k+1
		//k=n-len+1
	}
	
	//cout<<endl;
	cout<<dp[1][n];
}
signed main() {
	ios::sync_with_stdio();
	cin.tie(0);
	cout.tie(0);
	solve();
	return 0;
	
}

第四个

这道题蛮有意思的 我一开始的写法
是错的 只考虑了单向转移

比如说 1 5 4 5这个数据 我一开始最大是5 然后假设对1 5 转移 我认为现在差距是5-4=1
这很明显是错的 所以说我的想法有缺陷 只对了三个测试点
真正的做法 是有思维难度的

我发现现在很多题 我做的话都是只能看到狭窄的一面 不能看到全部 换句话说 不能以
上帝视角一般看透这个题目 从而做出半对或者错的思路 我打多校开最难的铜牌题也发现了这个现象 我不知道咋回事
就比如这个题来说 第二层循环直接开到-5000到5000就可以涵盖所有情况
再来说下这个dp数组的含义
dp i j i表示第几个物品 j的话指上减下的值 于是直接枚举-5000到5000即可
然后加个5010防止负数 这个j我没有捕捉到 导致没写出来

#include<bits/stdc++.h>
#define debug cout<<endl<<"--------"<<endl;
using namespace std;
const int range=1e3+10;
int n;
int up[range];
int down[range];
int dp[range][10110];
void solve()
{
	cin>>n;
	int sum=0;
	int ssum=0;
	for(int i=1;i<=n;i++)
		cin>>up[i]>>down[i];
	memset(dp,0x3f,sizeof dp);
	int w=dp[0][0];
	dp[0][0+5010]=0;
//	for(int i=1;i<=n;i++)
//	{
//		dp[0][up[i]-down[i]+5010]=0;
//	}
	for(int i=1;i<=n;i++)
	{
		for(int j=-5000;j<=5000;j++)
		{
			dp[i][j+5010]=min(dp[i-1][j+(up[i]-down[i])+5010],dp[i][j+5010]);
			dp[i][j+5010]=min(dp[i-1][j-(up[i]-down[i])+5010]+1,dp[i][j+5010]);	
		}
	}
	int ans=2147483647;
	for(int j=5010;j<=5010*2;j++)
	{
		if(dp[n][j]!=w)
		{
			ans=min(ans,dp[n][j]);
			break;
		}
	}
	for(int j=5010;j>=0;j--)
	{
		if(dp[n][j]!=w)
		{
			ans=min(ans,dp[n][j]);
			break;
		}
	}
	cout<<ans<<endl;
	return ;
}

第五个

这个题需要好好读下 意思是说有些地方有石头 尽力不去跳它
然后一看数据会发现太大了 数组根本模拟不了
即使是用map存 也存不下1e9的数据 因为st很小 其实1e9的点基本都可以走到 而且map顶多
存1e8个左右数据
这里得运用到一个数学知识 路径压缩
假设我们每次走p或者p+1。。。。。证明不来
反正是这样的 只要两个石头之间的距离大于s*t-s-t 以后的值都可以随便到

int s, t;
int m;
int a[range];
int x[range];
int b[range];
int  flag[range];
int dp[range];
void solve() {
	
	cin >> n;
	cin >> s >> t >> m;
	for (int i = 1; i <= m; i++) {
		cin >> x[i];
	}
	int dis=s*t-s-t+1+10;
//	cout<<dis<<endl;
	sort(x+1,x+1+m);
	int len=0;
	if(s==t)
	{
		for(int i=1;i<=m;i++)
		{
			b[i]=(x[i]-x[i-1])%s;
			len+=b[i];
			flag[len]+=1;
		}
		b[m+1]=(n-x[m])%s;
		len+=b[m+1];
	}
	else {
		for(int i=1;i<=m;i++)
		{
			b[i]=min(x[i]-x[i-1],dis);
			//	cout<<b[i]<<endl;
			len+=b[i];flag[len]=1;
		}
		b[m+1]=min(n-x[m],dis);
		len+=b[m+1];	
	}
	memset(dp,0x3f,sizeof dp);
	dp[0]=flag[0];
	for(int i=1;i<=len+11;i++)
	{	
		for(int j=s;j<=t;j++)
		{
			if(i-j>=0)
				dp[i]=min(dp[i-j]+flag[i],dp[i]);
		}
		//如果在i处我们有石子 我们要尽可能降低他
		//这样就不用纠结min的问题了 如果后面i+1可以取到不包含石头的
		//就会取
	}
	int  ans=2147483647;
	for(int i=len;i<=len-1+t;i++)
	{
		ans=min(dp[i],ans);
	}
	if(ans==2147483647)cout<<0<<endl;
	else 	cout<<ans<<endl;
	return ;
}
signed main() {
	solve();
}

参考题目小凯的疑惑

所以这个结论蛮重要的
然后知道这个结论就简单了 路径直接压缩成100就行 然后记录下总长度
状态方程是min dp[i]=dp[i-j]+flag[i],dp[i]
然后一定要注意s=t的情况是不适用这个数学结论的 因为他只能跳s的倍数

第六个

这个题有难度的 一定要意识到我们最终可以跳0-d步
但不是说可以往回跳 于是直接枚举到某一个地方时 我是从上一步是什么跳过来的
但是请注意 并不是说对于任何一个点来说 它可以来自于上一步0-d中的任何一步 这是不可能的
再着开一维时某一个点-j得到的值最大 如果这一个点不可以从上一步走j转移过来呢 你怎么
保证上一个人是走j j-1 j+1中的一个呢 所以不可以这么理想应当
我们二维记录某一个点上次走的步数 就是i-j走了j到了i
于是转移方程就是
dp[i][j]=max(dp[i-(j+d)][j-1],dp[i-(j+d)][j+1],dp[i-(j+d])[j],dp[i][j])
别问我为什么要max dp[i][j] 以后写dp 一定要写上 成为习惯 虽然这个题不写也没事

然后就是一堆细节 要加400 防止负数 再然后是d+1开始循环
还有这个

bool check(int j,int d)
{
	if(j<0){
		return j+d>0;
	}
	return 1;
}
	if (i-(j+d)>=0&&check(j,d))
	int maxn = 0;
	memset(dp, -0x3f, sizeof dp);
	//一定要赋值负无穷 不然就要出错 因为对于没发到的为0就会直接转移
	for (int i = 1; i <= n; i++)cin >> a[i], flag[a[i]] += 1, maxn = max(a[i], maxn);
	dp[d][400] = flag[0] + flag[d];
	int ans = dp[d][400];
	for (int i = d + 1; i <= maxn; i++) {
		for (int j = -350; j <= 350; j++) {
			if (i - (j + d) >= 0 && check(j, d)) {
				dp[i][j + 400] = max( dp[i - (j + d)][j + 400] + flag[i], max(dp[i - (d + j)][400 + j - 1] + flag[i], dp[i - (d + j)][400 + j + 1] + flag[i]));
				if (dp[i][j + 400] > ans) {
					ans = dp[i][j + 400];
					//	cout<<dp[i][j+400]<<" "<<i<<" "<<j<<" "<<i-(j+d)<<endl;
					//cout<<dp[i-(j+d)][400+j+1]<<" "<<dp[i-(j+d)][400+j-1]<<" "<<dp[i-(j+d)][400+j]<<endl;
				}

			}

			else continue;
		}
	}

第七个

div2的题了 对于只有2次的数这种难办 我没想到
后面看了jly的代码 只需要预处理两次就行 真笨!不过我有信心场切 因为这个预处理是可以想出来的 很妙

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define debug cout<<endl<<"----------"<<endl;
using namespace std;
const int range = 3e5 + 10;
int n;
bool vis[range];
int a[range];
void init()
{
	for(int i=1;i<=n;i++){
		vis[a[i]]=0;
		a[i]=0;
	}
}
/*

1
6
1 1 2 3 2 3

12 
0 1 1 1 2 3 

20 
0 0 1 1 1 1 

24 
0 0 0 1 1 1

27
0 0 0 0 1 1

29
0 0 0 0 0 1 
30 
0 0 0 0 0 0   
*/
void solve(int t) {
	
	cin >> n;
	priority_queue<int>q;
	map<int, int>ma;
	map<int,int>len;
	//块长
	int sum = 0;	
	for(int i=1;i<=n;i++)cin>>a[i];
	for (int i = 1; i <= n; i++) {
		ma[a[i]]++;
		if (ma[a[i]] >= 2&&a[i]!=0)q.push(a[i]);
		sum += a[i];
		if (q.size()) {
			a[i] = q.top();
//			len[a[i]]++;
		}
		else a[i]=0;
	}		
	priority_queue<int>qq;
	ma.clear();
	for (int i = 1; i <= n; i++) {
		if(a[i]==0)continue;
		ma[a[i]]++;
		if (ma[a[i]] >= 2&&a[i]!=0)qq.push(a[i]);
		sum += a[i];
		if (qq.size()) {
			a[i] = qq.top();
			len[a[i]]++;
		}
		else a[i]=0;
	}	
	for(int i=1;i<=n;i++)
	{
		if(a[i]==0)continue;
		if(vis[a[i]])continue;
		if(vis[a[i]]==0)
		{
			int r=len[a[i]];
			if(r==1){
				sum+=a[i];
				vis[a[i]]=1;
				continue;
			}
			//块长
			int c=n-(i+r-1);//块剩下的延申长度		
			if(c<=0)c=0;
			int dc=(1+r)*(r)/2;//最后消失长度
			sum+=c*(r*a[i])+dc*a[i];
			vis[a[i]]=1;
		}
	}
	cout<<sum<<endl;
	init();
}

第八个

这个是树直径 模板了

怎么做?要学会两种求树直径的办法即可 一种dp 一种dfs 其中dfs无法处理负边权
算了 明天写吧 睡觉了

#include<bits/stdc++.h>
#define int long long 
#define debug cout<<endl<<"--------"<<endl;
using namespace std;
const int range=2e5+10;
int n;
int a[range];
int cost[range];
struct node{
	int v,w;
};
vector<node>e[range];
int ans=-1e9;
int  dfs(int x,int fa)
{
	int d1=0;
	int d2=0;
	for(auto i:e[x])
	{
		int v=i.v;
		if(v==fa)continue;
		int d=dfs(v,x)+i.w;
	//	debug
	//	cout<<d<<" "<<v<<" "<<x<<endl;
	//	cout<<i.w<<" "<<fa<<endl;
		if(d>=d1){//别忘了等号
			d2=d1;
			d1=d;		
	//		debug
	//	cout<<v<<" "<<x<<" "<<fa<<" "<<d1<<" "<<d2<<endl; 
		}
		else if(d>d2)d2=d;
	}
	ans=max(ans,d2+d1+cost[x]);
	return d1;
	//dp写法  dfs无法处理负边权
	//我觉得也可以弄一个点出来专门连1 然后存好边权写
}
void solve()
{
	cin>>n;	
	for(int i=1;i<=n;i++){
		cin>>cost[i];
	}
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		e[x].push_back({y,cost[y]});
		e[y].push_back({x,cost[x]});
	}
	dfs(1,0);
	cout<<ans<<endl;
	return ;
}
signed main()
{
	solve();
}

上面是dp做法
下面是dfs

const int N = 10000 + 10;

int n, c, d[N];
vector<int> E[N];

void dfs(int u, int fa) {
  for (int v : E[u]) {
    if (v == fa) continue;
    d[v] = d[u] + 1;
    if (d[v] > d[c]) c = v;
    dfs(v, u);
  }
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i < n; i++) {
    int u, v;
    scanf("%d %d", &u, &v);
    E[u].push_back(v), E[v].push_back(u);
  }
  dfs(1, 0);
  d[c] = 0, dfs(c, 0);
  printf("%d\n", d[c]);
  return 0;
}

我直接从oi wiki搬了 代码在我台式机上 反正点权其实可以联想成边权的

第九题

这是树的最小支配 是模板题了
但是挺难的
对于儿子与父亲有三种关系
父亲选 儿子随便
父亲不选 靠儿子
父亲不选 靠爷爷

应该开dp[i][3]表示
对于父亲选的话 初始值是1
转移方程式
dp[i][1]+=min(dp[j][1],dp[j][2],dp[j][3])
对应儿子的选 不选三个情况
dp[i][3]+=min(dp[j][1],dp[j][2])

对于dp[i][2]最麻烦了
父亲不选 靠儿子 儿子就必须要选一个
那是不是说 我只需要选一个mini的dp[j][1]?
不是的
dp[j][1]:89 10
dp[j][2]:88 6
这种情况如果我们选10+88 显然不是最优的 最优的应该是
89+6
所以对于我们全选dp[j][2]的时候 不存在选dp[j][1]的情况
我们需要让其中一个dp[j][2]变成dp[j][1] 这个就是inc了

如果我们已经选了dp[j][1]那就无所谓了
那么这个inc怎么处理呢 很简单
inc=min(inc,dp[j][1]-dp[j][2])
如果成负数就证明了 肯定选了dp[j][1] 如果是正数 选最小的即可
于是
dp[i][2]+=min(dp[j][1],dp[j][2]);
if(inc)dp[i][2]+=inc;
对于dp[i][2]初始值是0
然后这题就写完了

第十题

这题有意思
其实树形dp结合背包来考是很正常的

这道题设立dp数组 不要想多开一维状态表示这个枝条有没有被剪
这个dp数组
dp[i][j]表示i这个点还剩多少个枝条
联想背包 j就像体积一样

那这个转移方程式该怎么思考呢
很明显j可以枚举0-Q
然后又由于他有很多个儿子 (假设题目变成多叉)
我们就得思考到 对一个儿子k而言
可以选多少个呢 假设父亲选了j个体积 分配到儿子的体积又是多少呢
很明显是j-父亲 父亲连接儿子的这一个代价是1
所以是j-1
这个for循环就是k=0;k<j这样写的 于是我们就可以开始写代码了

void dfs(int x, int fa) 
{
	dp[x][0]=0;
	for (auto v : e[x]) {
		if (v == fa)continue;
		dfs(v, x);
		for (int j = q; j >= 0; j--) {
			for (int k = 0; k < j; k++) {
		dp[x][j] = max(dp[x][j], dp[x][k] + dp[v][j - k - 1] + a[x][v]);
	//	ans = max(ans, dp[x][j]);//树枝也要算一条的 
			}
		}
	}
}

然后还有一个细节 就是我们对于j要倒着遍历为什么呢?
假设我们正着遍历 对于
dp[i][5] k=3
dp[i][5]=max(dp[i][3]....)
试想下这个dp[i][3]是哪里来的呢 这个3表示留给x自己别的儿子的 如果正着遍历 3这个状态早就被修改了 轮到5的时候 有可能这个3就是此时这个儿子的状态得来的 因为取得max啊 我们倒着就可以确保5比3早更新

第十一题

来到状态压缩的题目
啊 还有好多别的平台的题没弄呀
下午又多校

这题用的是二进制的思想 先提前预处理同一行可以放的那种情况 使用二进制就行思考 因为n比较小
比如1001 这种就可以放 1100就不行 因为互相会攻击到 于是我们可以通过预处理 处理得到在1<<n都行的那种 因为n列于是有1<<n-1种二进制
然后开一个num数组记录每一种方式需要几个国王
for循环需要枚举上一层所选的种类
然后a>>1&b!=1 a&b!=1 a<<1&b!=1
肯定还有就是体积这一块了 肯定也是要for循环枚举的
还有就是num[a]<=j
最终转移方程式就是
dp[i][j][a]+=dp[i-1][j-num[a][b]
表示以a为状态 上一行以b为状态 此时可以放j个的dp方程式

然后这边要注意初始时0也要算一个可行的状态不然没法进行初始状态1 2 4这种的可行状态的dp[1][j][a]的转移 可以这么理解0就是上一行啥也没放 这也算啊

第十二题

下午多校 又是坐牢
这道题其实和上一题很像 只不过多一个状态而已 多一个上上行而已
但是少了一个q 所以我们可以试着确定dp数组
dp[i][a][b]表示此行a上一行b 于是和dp[i-1][b][c]有关
然后提前预处理同行即可 别的注意事项都很正常了 个人认为题目都很好
建议重做

第十三个

数位dp
对于数位dp一定要有dp[b]-dp[a-1]这种思想

然后考虑预处理
dp[i][j]表示i位数是j 此时若 abs(k-j)>=2就要+上dp[i-1][k]
要考虑前导0因为 我们计算答案要用到02 036.。。。这种 只需要
calc函数的时候 首位从1开始即可

然后在写calc函数一定要注意 我们计算两种情况

一种是位数小于这个a的和位数等于a的
对于位数等于a为我们只需要一位位计算 last=now不断更新即可
还要if i==1 的判断 表示a自己就是
还有很多题啊啊啊

位数小于a 直接for开始加

牛客多校

第一场多校的题目

首先要读题读懂 只需要满足这个序列有即可 不是整个序列都&1

考虑二进制拆位思考
我们当时的错误思路就是以为要m个 我先找到2的情况 然后捆绑法2*一个(2^m)的数就是选三的情况,很明显 是会重复的

然后就g了

讲下正确思路吧 对了 我们一群人一开始都认为偶数没用。。。

正确思路是假设选k个奇数 n-k个偶数

则对于二进制的每一位而言都是 2^k-1 -1是所有人都选了1 那&就错了 (m-1)位要弄
奇数:(2k-1)(m-1)次
偶数是2(n-k)(m-1) 不用-1

请注意 这里的所有数字顺序已经所有都包含进去了

然后就是考虑奇数的摆放位置了 C(n,k)就可以了

此题就结束了 很好的一道题
个人认为绿

 cin>>n>>m>>mod;
    init();
    pw[0]=1;
    for(int i=1;i<=5000;i++)
    {
        pw[i]=(1LL*pw[i-1])*2%mod;
    }
    //c(n,i)*2(n-i)^m-1*(2^k-1)m-1
    long long  ans=0;
    for(int i=1;i<=n;i++)
    {
        long long  one=1;
        long long two=1;
        for(int j=1;j<=m-1;j++)
        {
            one=(1LL*one*pw[n-i])%mod;
            one%=mod;
            two=(1LL*two*(pw[i]-1))%mod;
            two%=mod;       
        }
        ans=(ans+((one*two%mod)*(1LL*c[n][i]))%mod)%mod;
    //  cout<<one<<" "<<two<<" "<<c[n][i]<<endl;
    //  cout<<ans<<endl;
    }
    cout<<ans<<endl;

多校


这个题是真可惜
很像那个之前的题

#include<bits/stdc++.h>
#define int long long 
#define debug cout<<endl<<"--------"<<endl;
using namespace std;
const int range=1e6+10;
int n;
int a[range];
map<pair<int,int>,int>ma;
int x,y;
string s;
void solve()
{
	cin>>n;
	cin>>x>>y;
	cin>>s;
	int tx=0;		
	int ty=0;
	if(x==0&&y==0){
		int sum=0;
		sum+=(n+1)*n/2;
		cout<<sum<<endl;
		return ;
	}
	ma[{0,0}]++;
	int ans=0;
	for(int i=0;i<=n-1;i++)
	{
		if(s[i]=='A')tx--;
		else if(s[i]=='D')tx++;
		else if(s[i]=='W')ty++;
		else if(s[i]=='S')ty--;
		ma[{tx,ty}]++;
		pair<int,int> temp;
		temp.first=tx-x;
		temp.second=ty-y;
		if(ma[temp]!=0)
		{
			ans+=ma[temp]*(n-i);
			ma[temp]=0;
			
		}
	}
	cout<<ans<<endl;
	return ;
}

Here

这题是个二维的版本
只需要想到-x,-y这个差分 那么就做出了
可惜可惜

多校

这个题就更可惜了
wa了8发 ,我咋就没想到我们可以求出一个最大来回次数啊
然后那个次数求出来NUM*(R-L)+R 就可以了
我就是没想到这个num可以求 吐辣
可以二分 也可以直接代数去求

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define debug cout<<endl<<"----------"<<endl;
using namespace std;
const int range = 6e5+10;
int n;
int ll, rr;
int a[range];
bool check(int mid) {
	int s = 0;
	for (int i = 1; i <= n; i++) {
		s += min(a[i], mid);
	}
	return s >= mid * ll;
}
void solve() {
	cin >> n >> ll >> rr;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		a[i] = (a[i] - 1) / 2;
	}
	int l = 0;
	int ans = 0;
	int r = 5e5;
	while (l <= r) {
		int mid = l + r >> 1;
		if (check(mid)) {
			ans = max(ans, mid);
			l = mid + 1;
		} else r = mid - 1;
	}
	if (ans * (rr - ll) + rr >= n)cout << "YES" << endl;
	else cout << "No" << endl;
	return ;
}

希望重做

多校

这个题很可惜 大家没想到一个集合的概念 眼睛只看到了4这个操作
没用想到长度>4时 我们完全可以把他们拼起来 很可惜
赛后使用并查集通过 求出集合长度即可


#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
#define debug cout<<endl<<"----------"<<endl;
using namespace std;
const int range=3e6+10;
int n;
//int a[range];
int f[range];
int  find1(int i)
{
	if(i==f[i])return i;
	else 
	{//下面这一步可不能省略 不然不能体现路径压缩
		f[i]=find1(f[i]);
		return f[i];		
	}
}
int cnt[range];
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)f[i]=i;
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x;
		int a=find1(x);
		int b=find1(i);
		if(a==b)continue;
		f[a]=b;
	}
	for(int i=1;i<=n;i++)
	{
		//还要进行一次重新融合
		cnt[find1(f[i])]++;
	}
	int ans=0;int w=0;
	for(int i=1;i<=n;i++)
	{
		ans+=cnt[i]/3;
		if(cnt[i]%3==2)w++;
	}
	ans+=(w/2)+w%2;
	cout<<ans<<endl;
}

状态压缩位运算总结


左边就不讲了
右边
最后一位取反 1001^1=1000
把右数第k位变成1 比如

XXXX  0  00011XXX
      ^ 

指针表示第k位 那么如果仅仅把他变成1 那么别的位就不能动
首先是1<<k-1得到第k位是1后面全是0 然后呢 一个|就可以了

那么变成0其实也很简单 &0不就好啦 先得到1000这种的
为什么k-1 因为你想我们要第3位实则是二进制的的2的平方那位 因为我们第0位是1呀

然后~表示取反 1000->0111这样 于是就好了

右数第k位取反
比如

XXXX  0  00011XXX
      ^ 

我们xor的话 是0变成1 是1变成0 就行了
也不会影响别人

这是进阶版本了

取末k位

x&((1<<k)-1)
1<<k-1会得到2^K-1的数 然后&即可得到了 

取右数第k位
移动k-1位来到第一位即可

把末k位变成1
这个就不用讲了 切记一个原则第k位并不是说是2^k!
比如第三位是2的平方

末k位取反
取反使用xor即可

把右边连续的1变成0
首先解释下为什么+1 对于那些奇数+1 可以得到进位
比如
10111
变成
11000
然后10111&11000 就可以了

如果是偶数 10110&10111也是可以的 记住是右边连续的1

把右起第一个0变成1 也是同理
连续的0变成1 比如10000 -1=01111 然后一个|就可以了

100001 -1=100000 | 10000 也是一样的 奇数的话 没办法 第一个就是1了
没有连续的说法 是中断的

取右边连续的1

10111 +1 =11000 二者异或 得到1111然后右移 因为刚才有进位
10110 +1 =10111 二者异或 得到111 因为+1了 那个不要的 所以右移动

最后一个也很重要 传说中的lowbit
每次取最右边的1后面的元素包括1了
x&-x 首先来说下负数怎么弄
首先明确 符号位是指最高位
对于-5
负数的补码 就是正数的,然后在第32位弄成1 得到原码
10000000 00000000 00000000 00000101
然后除了最高位全部取反得到1000000000000.......01010
然后最后一位+1即可得到了-5的二进制
1000000000000.......01011

0101&1011=1 于是变成了4 100
100->011+1 100 然后就是4

就这样了

posted @ 2024-07-24 23:23  LteShuai  阅读(33)  评论(0)    收藏  举报