最近做题小结-含没有总结的牛客视频以及各个平台题目-已经解决
前言
来到产业园第一次写题 最近做的题目很杂 涉及很多个平台 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
就这样了

浙公网安备 33010602011771号