联训题单 / 集训杂题纪要

news/2024/11/14 17:55:18/文章来源:https://www.cnblogs.com/HaneDaCafe/p/18445931

UPD: 新增了杂题选改栏

总览

题单 进度 备注
数据结构1 4/24 数据结构可爱捏 >_<
搜索 模拟 All Clear/10 搜索可爱捏 >_<
数学1 0/11 数学不可爱捏 `-´
字符串 6/13 哈希可爱捏 >_<
杂题选改 7 杂题专题没了,杂题倒是有不少

数据结构 1

STEP

读假题了,读成下面这样了:

给定 01 序列,每次单点修改,查询最长的字符相同的连续段长度

这不是一眼线段树经典板子题,分别维护左右区间信息以及合并处的信息,然后尝试在中间合并来更新答案

十五分钟光速打完拉下样例来发现不对,然后才发现自己读假题了

随后发现这俩题难道不是一个做法吗,只不过你要找的是 01010 这样的而不是 11111 这样的,所以你只有在不同的时候才能合并左右区间

所以改了个符号就过了

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
namespace stree{struct tree{int l,r;int maxsize;int slize,srize;bool lch,rch;}t[800001];#define tol (id*2)#define tor (id*2+1)#define mid(l,r) mid=((l)+(r))/2void pushup(int id){t[id].maxsize=max({t[tol].maxsize,t[tor].maxsize,t[tol].slize,t[tor].slize,t[tol].srize,t[tor].srize,});if(t[tol].rch!=t[tor].lch){t[id].maxsize=max(t[id].maxsize,t[tol].srize+t[tor].slize);}if(t[tol].slize==t[tol].r-t[tol].l+1 and t[tol].rch!=t[tor].lch){t[id].slize=t[tol].slize+t[tor].slize;}else{t[id].slize=t[tol].slize;}if(t[tor].srize==t[tor].r-t[tor].l+1 and t[tol].rch!=t[tor].lch){t[id].srize=t[tol].srize+t[tor].srize;}else{t[id].srize=t[tor].srize;}t[id].lch=t[tol].lch;t[id].rch=t[tor].rch;}void build(int id,int l,int r){t[id].l=l;t[id].r=r;if(l==r){t[id].maxsize=t[id].slize=t[id].srize=1;t[id].lch=t[id].rch=0;return;}int mid(l,r);build(tol,l,mid);build(tor,mid+1,r);pushup(id);}void change(int id,int p){if(t[id].l==t[id].r){int res=t[id].lch^1;t[id].lch=t[id].rch=res;return;}if(p<=t[tol].r) change(tol,p);else change(tor,p);pushup(id);}inline int ask(){return t[1].maxsize;}
}
int main(){scanf("%d %d",&n,&q);stree::build(1,1,n);while(q--){int x;scanf("%d",&x);stree::change(1,x);printf("%d\n",stree::ask());}
}

三元上升子序列

\(f2_i\) 表示以 \(i\) 结束的二元上升子序列个数,\(f3_i\) 表示以 \(i\) 结束的三元上升子序列个数,则不难有转移方程

\[f2_i=\sum_{\{j|j\lt i,a_j\lt a_i\}}1 \]

\[f3_i=\sum_{\{j|j\lt i,a_j\lt a_i\}}f2_j \]

对于内层的处理直接开一个值域线段树,从前到后在对应值域处插入答案,查询直接插 \(1\) 到当前值前缀和即可

因为有俩方程所以开了两颗线段树

要注意判当 \(a_i=1\)\(a_i-1=0\) 的问题,我懒得判了直接全部都加一了,整体平移答案不变

不开 long long 见祖宗

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int a[30001];
int f2[30001],f3[30001];
int ans=0;
struct stree{struct tree{int l,r;int sum;}t[400001];#define tol (id*2)#define tor (id*2+1)#define mid(l,r) mid=((l)+(r))/2void build(int id,int l,int r){t[id].l=l;t[id].r=r;if(l==r){return;}int mid(l,r);build(tol,l,mid);build(tor,mid+1,r);}void change(int id,int p,int val){if(t[id].l==t[id].r){t[id].sum+=val;return;}if(t[tol].r>=p) change(tol,p,val);else change(tor,p,val);t[id].sum=t[tol].sum+t[tor].sum;}int ask(int id,int l,int r){if(l<=t[id].l and t[id].r<=r){return t[id].sum;}if(r<=t[tol].r){return ask(tol,l,r);}else if(l>=t[tor].l){return ask(tor,l,r);}int mid(t[id].l,t[id].r);return ask(tol,l,mid)+ask(tor,mid+1,r);}
};
stree x,y;
signed main(){scanf("%d",&n);x.build(1,1,100004);y.build(1,1,100004);for(int i=1;i<=n;++i){scanf("%lld",&a[i]);a[i]++;}for(int i=1;i<=n;++i){ans+=y.ask(1,1,a[i]-1);x.change(1,a[i],1);y.change(1,a[i],x.ask(1,1,a[i]-1));}cout<<ans;
}

线段树分裂

你们有什么头绪吗

线段树分裂也能用类似 FHQ 的思路去解

只是有点难调

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
namespace stree{struct tree{int tol,tor;int val;}t[4000001];int tot;inline int newnode(){tot++;return tot;}#define mid(l,r) mid=((l)+(r))/2void change(int&id,int l,int r,int pos,int val){if(!id) id=newnode();t[id].val+=val;if(l==r){return;}int mid(l,r);if(pos<=mid) change(t[id].tol,l,mid,pos,val);else change(t[id].tor,mid+1,r,pos,val);}int ask(int id,int l,int r,int L,int R){if(R<l or r<L) return 0;if(L<=l and r<=R){return t[id].val;}int mid(l,r);return ask(t[id].tol,l,mid,L,R)+ask(t[id].tor,mid+1,r,L,R);}int kth(int id,int l,int r,int k){if(l==r) return l;int mid(l,r);if(t[t[id].tol].val>=k) return kth(t[id].tol,l,mid,k);return kth(t[id].tor,mid+1,r,k-t[t[id].tol].val);}int merge(int x,int y){if(x==0) return y;if(y==0) return x;t[x].val+=t[y].val;t[x].tol=merge(t[x].tol,t[y].tol);t[x].tor=merge(t[x].tor,t[y].tor);return x;}void split(int x,int &y,int k){if(x==0) return;y=newnode();int now=t[t[x].tol].val;if(k>now) split(t[x].tor,t[y].tor,k-now);else swap(t[x].tor,t[y].tor);if(k<now) split(t[x].tol,t[y].tol,k);t[y].val=t[x].val-k;t[x].val=k;return;}
}
int root[200001];
int seq=1;
signed main(){scanf("%lld %lld",&n,&m);for(int i=1;i<=n;++i){int x;scanf("%lld",&x);stree::change(root[1],1,n,i,x);}while(m--){int op;scanf("%lld",&op);int x,y,z;if(op==0){scanf("%lld %lld %lld",&x,&y,&z);int k1=stree::ask(root[x],1,n,1,z);int k2=stree::ask(root[x],1,n,y,z);int tmp=0;stree::split(root[x],root[++seq],k1-k2);stree::split(root[seq],tmp,k2);root[x]=stree::merge(root[x],tmp);}if(op==1){scanf("%lld %lld",&x,&y);root[x]=stree::merge(root[x],root[y]);}if(op==2){scanf("%lld %lld %lld",&x,&y,&z);stree::change(root[x],1,n,z,y);}if(op==3){scanf("%lld %lld %lld",&x,&y,&z);printf("%lld\n",stree::ask(root[x],1,n,y,z));}if(op==4){scanf("%lld %lld",&x,&y);if(stree::t[root[x]].val<y){printf("-1\n");continue;}printf("%lld\n",stree::kth(root[x],1,n,y));}}
}

搜索 模拟

小木棍

这题的搜索方法很关键啊

  • 如果你枚举所有的短木棍放到哪个长木棍里,那么你大概率会 T 掉
  • 如果你枚举所有的长木棍被哪些短木棍拼成,那么你大概率会被卡常

事实上应该搜所有的长木棍被哪些长度的短木棍拼成

这样你只需要对值域开桶就好了,会少很多搜索次数

剪枝

  • 从大到小排序,防止后面的值过大导致不合法情况太多
  • 如果当前长木棍完全没填或者没填的正好等于当前剩余值,那么直接退,后面一定没有合法解

因为你用这个如果正好能卡上剩下的部分,这是一个非常优的解,如果这个都实现不了,反而要用比它小的木棍来凑,那么给后面剩下的木棍一定更少,显然还是不合法的

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
int tot;
int maxn,minn=0x3f3f3f3f;
int a[101],cnt[101]; 
bool dfs(int res,int eachsum,int _tot,int sum,int lastchoose){if(res>_tot) return true;if(sum==eachsum){return dfs(res+1,eachsum,_tot,0,maxn);}for(int i=min(eachsum-sum,lastchoose);i>=minn;i--){if(cnt[i]){cnt[i]--;if(dfs(res,eachsum,_tot,sum+i,i)) return true;cnt[i]++;if(sum==0 or sum+i==eachsum){return false;}}}return false;
}
int main(){cin>>n;for(int i=1;i<=n;++i){cin>>a[i];cnt[a[i]]++;tot+=a[i];maxn=max(maxn,a[i]);minn=min(minn,a[i]);}for(int i=maxn;i<=tot/2;i++){if(tot%i==0){if(dfs(1,i,tot/i,0,maxn)){cout<<i;return 0;}}}cout<<tot;    
}

Pushing Boxes

这个题的要求比较特殊,是在保证最小推箱子次数前提下的移动步数最小

所以我们对广搜开一个优先队列,然后以推箱子次数为第一关键字排序,这样可以保证第一次遇到的答案符合要求

然后在广搜里要注意顺序,优先走路,再考虑周围是否有箱子以及推箱子

  • 走路的时候要注意目的地是否有箱子
  • 方案输出直接采用拼接字符串的方式,字符串直接扔进优先队列里就行了

由于 POJ 实在是太史了,所以你需要尽量减少不必要的入队次数,即入队的时候判掉 vis,否则就会 TLE,但是入队的时候判掉 vis 是假的

Hack:

6 9
T......##
#..##...#
#.###....
#.####...
.......BS
#..####..
0 0

Except Output:

WWWWWWswNNNNenW

去找 huge 换了 UVA 的源,这样能稍微解决一点,否则就只能 Astar 了

点击查看代码
#include<iostream>
#include<queue>
#include<cstring>
#include<math.h>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
namespace hdk{
int n,m;
int mp[21][21];
// -1 stand for rock
// 0 stand for space
// 1 stand for box
// 2 stand for me
// 3 stand for target
inline int __getchar(){char ch=getchar();while(ch!='#' and ch!='.' and ch!='T' and ch!='B' and ch!='S') ch=getchar();if(ch=='#') return -1;if(ch=='.') return 0;if(ch=='B') return 1;if(ch=='S') return 2;return 3;
}
struct node{int x,y;inline bool operator ==(const node &A){return x==A.x and y==A.y;}inline bool operator !=(const node &A){return !(x==A.x and y==A.y);}inline node(){}inline node(int _x,int _y){x=_x;y=_y;}
};
node b,s,t;
struct nnode{int move_times,push_times;node box,me;string method;inline bool operator <(const nnode &A)const{if(push_times==A.push_times) return move_times>A.move_times;return push_times>A.push_times;}inline nnode(int x,int y,int bx,int by,int mx,int my,string met){move_times=x;push_times=y;box=node(bx,by);me=node(mx,my);method=met;}
};
inline bool ill(int x,int y){if(x<1 or x>n) return false;if(y<1 or y>m) return false;return true;
}
inline bool between(int x1,int y1,int x2,int y2){if(x1==x2) return abs(y1-y2)==1;if(y1==y2) return abs(x1-x2)==1;return false;
}
int dx[5]={0,0,0,1,-1};
int dy[5]={0,1,-1,0,0};
char box_info[5]={' ','E','W','S','N'};
char mov_info[5]={' ','e','w','s','n'};
bool vis[21][21][21][21];
inline nnode bfs(nnode start){priority_queue<nnode>q;q.push(start);vis[start.box.x][start.box.y][start.me.x][start.me.y]=true;while(!q.empty()){nnode u=q.top();q.pop();if(u.box==t) return u;for(int i=1;i<=4;++i){if(ill(u.me.x+dx[i],u.me.y+dy[i])){if(node(u.me.x+dx[i],u.me.y+dy[i])!=u.box){if(mp[u.me.x+dx[i]][u.me.y+dy[i]]!=-1){if(!vis[u.box.x][u.box.y][u.me.x+dx[i]][u.me.y+dy[i]]){vis[u.box.x][u.box.y][u.me.x+dx[i]][u.me.y+dy[i]]=true;q.push(nnode(u.move_times+1,u.push_times,u.box.x,u.box.y,u.me.x+dx[i],u.me.y+dy[i],u.method+mov_info[i]));}}}}}if(between(u.box.x,u.box.y,u.me.x,u.me.y)){for(int i=1;i<=4;++i){if(node(u.me.x+dx[i],u.me.y+dy[i])==u.box){if(ill(u.me.x+dx[i]*2,u.me.y+dy[i]*2)){if(mp[u.me.x+dx[i]*2][u.me.y+dy[i]*2]!=-1){if(!vis[u.me.x+dx[i]*2][u.me.y+dy[i]*2][u.box.x][u.box.y]){vis[u.me.x+dx[i]*2][u.me.y+dy[i]*2][u.box.x][u.box.y]=true;q.push(nnode(u.move_times,u.push_times+1,u.me.x+dx[i]*2,u.me.y+dy[i]*2,u.box.x,u.box.y,u.method+box_info[i]));}}}}}}}return nnode(-1,-1,0,0,0,0,"Impossible.");
}
inline void main(){int cnt=0;while(1){cnt++;cin>>n>>m;if(n==0 and m==0) break;memset(vis,0,sizeof vis);for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){mp[i][j]=__getchar();if(mp[i][j]==1) b=node(i,j);if(mp[i][j]==2) s=node(i,j);if(mp[i][j]==3) t=node(i,j);if(mp[i][j]!=-1) mp[i][j]=0;}}cout<<"Maze #"<<cnt<<'\n';cout<<bfs(nnode(0,0,b.x,b.y,s.x,s.y,"")).method<<"\n\n";}
}
}
int main(){hdk::main();
}

斗地主 加强版

甚好

首先如果不动脑子地打暴力会得到这么一坨东西

#include<bits/stdc++.h>
using namespace std;
int t,n;
struct node{int x,y;
};
node a[24];
struct cthin{template<typename T>cthin& operator >>(T&x){cin>>x;return *this;}cthin& operator >>(node&x){cin>>x.x>>x.y;return *this;}
}cthin;
struct cthout{template<typename T>cthout& operator <<(const T x){cout<<x;return *this;}
}manbaout;
int ans=0;
int cnt[16];
#define dw cnt[14]
#define xw cnt[15]
inline int mod(int x){while(x>13) x-=13;return x;
}
void dfs(int tot,int pcnt){// cout<<"dfs "<<tot<<" "<<pcnt<<endl;if(pcnt==n){ans=min(ans,tot);return;}if(tot>=ans) return;for(int k=5;k<=12;++k){for(int i=1;i<=13;++i){bool flag=true;for(int j=i;j<=i+k-1;++j){if(j==2 or cnt[mod(j)]<1){flag=false;break;}}if(flag){for(int j=i;j<=i+k-1;++j){cnt[mod(j)]--;}dfs(tot+1,pcnt+k);for(int j=i;j<=i+k-1;++j){cnt[mod(j)]++;}}}}for(int k=3;k<=12;++k){for(int i=1;i<=13;++i){bool flag=true;for(int j=i;j<=i+k-1;++j){if(j==2 or cnt[mod(j)]<2){flag=false;break;}}if(flag){for(int j=i;j<=i+k-1;++j){cnt[mod(j)]-=2;}dfs(tot+1,pcnt+k*2);for(int j=i;j<=i+k-1;++j){cnt[mod(j)]+=2;}}}}for(int k=2;k<=12;++k){for(int i=1;i<=13;++i){bool flag=true;for(int j=i;j<=i+k-1;++j){if(j==2 or cnt[mod(j)]<3){flag=false;break;}}if(flag){for(int j=i;j<=i+k-1;++j){cnt[mod(j)]-=3;}dfs(tot+1,pcnt+k*3);for(int j=i;j<=i+k-1;++j){cnt[mod(j)]+=3;}}}}for(int i=1;i<=15;++i){if(cnt[i]>=4){cnt[i]-=4;for(int j=1;j<=15;++j){if(cnt[j]>=1){cnt[j]--;for(int k=1;k<=15;++k){if(cnt[k]>=1){cnt[k]--;dfs(tot+1,pcnt+6);cnt[k]++;}}cnt[j]++;}}for(int j=1;j<=15;++j){if(cnt[j]>=2){cnt[j]-=2;for(int k=1;k<=15;++k){if(cnt[k]>=2){cnt[k]-=2;dfs(tot+1,pcnt+8);cnt[k]+=2;}}cnt[j]+=2;}}cnt[i]+=4;}}bool vis[16]={};for(int k=4;k>=1;--k){for(int i=1;i<=15;++i){if((!vis[i]) and cnt[i]>=k){cnt[i]-=k;vis[i]=true;dfs(tot+1,pcnt+k);cnt[i]+=k;}}}for(int i=1;i<=15;++i){if(cnt[i]>=3){cnt[i]-=3;for(int j=1;j<=15;++j){if(cnt[j]>=1){cnt[j]--;dfs(tot+1,pcnt+4);cnt[j]++;}}cnt[i]+=3;}}for(int i=1;i<=15;++i){if(cnt[i]>=3){cnt[i]-=3;for(int j=1;j<=15;++j){if(cnt[j]>=2){cnt[j]-=2;dfs(tot+1,pcnt+5);cnt[j]+=2;}}cnt[i]+=3;}}if(xw){xw=0;dfs(tot+1,pcnt+1);xw=1;}if(dw){dw=0;dfs(tot+1,pcnt+1);dw=1;}if(xw and dw){xw=0;dw=0;dfs(tot+1,pcnt+2);xw=1;dw=1;}
}
signed main(){ios::sync_with_stdio(false);cthin>>t>>n;while(t--){memset(cnt,0,sizeof cnt);ans=0x7fffffff;for(int i=1;i<=n;++i){cthin>>a[i];if(a[i].x!=0){cnt[a[i].x]++;}else{if(a[i].y==1) xw=1;else dw=1;}}dfs(0,0);cout<<ans<<'\n';}
}

依照题意模拟即可,打不出来建议退役

然后考虑怎么优化,发现这玩意根本优化不动

实际上需要换一种思路,去枚举出哪种牌而不是一次次判断能出什么牌,我们按编号从小到大依次打出该编号的牌,能打就打,打完了再考虑怎么打完后面的牌(不是说这个牌打不完就不能打后面的,而是优先进行能打完当前牌的操作),这样能省去很多不必要的状态

但是你这么打又出问题了,被 hack 掉了

44666655

你会发现,如果你以编号递增顺序优先出牌,那么你就会出成对子+对子+炸弹的形式,然而正解是四带二对

为了避免这种情况,所以考虑把四带二对拆成两种情况:

  • 4+2+2
  • 2+4+2

其他的能拆分的操作同理,最后一共拆出来的情况如下

  • 4+1+1
  • 4+2+2
  • 4
  • 三顺子
  • 3+1
  • 3+2
  • 3
  • 双顺子
  • 2+4+2
  • 2+3
  • 2
  • 顺子
  • 1+3
  • 1
  • 火箭

然后加一点剪枝

  • 如果当前状态答案已经劣于当前最优解了,直接返回
  • 优先搜出牌数量大的,即按照 4->3->2->1 的顺序搜

比较方便的一个技巧是,把 A,2 挪到整副牌最后(大小王之前),这样做顺子的时候方便一点

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int t,n;
struct node{int x,y;
};
node a[24];
struct cthin{template<typename T>cthin& operator >>(T&x){cin>>x;return *this;}cthin& operator >>(node&x){cin>>x.x>>x.y;return *this;}
}cthin;
struct cthout{template<typename T>cthout& operator <<(const T x){cout<<x;return *this;}
}manbaout;
int ans=0;
int cnt[16];
#define dw cnt[14]
#define xw cnt[15]
inline int mp(int x){if(x==1) return 12;if(x==2) return 13;return x-2;
}
inline int mod(int x){while(x>13) x-=13;return x;
}
void dfs(int now,int tot,int pcnt){if(tot>ans) return;    //剪枝if(pcnt==n){ans=min(ans,tot);return;}if(now>15) return;if(cnt[now]==0) dfs(now+1,tot,pcnt);if(dw and xw){dw--;xw--;dfs(now,tot+1,pcnt+2);dw++;xw++;}if(cnt[now]>=4){cnt[now]-=4;for(int i=1;i<=13;++i){   //4 + 1 + 1if(cnt[i]>=1){cnt[i]--;for(int j=i;j<=15;++j){if(cnt[j]>=1){cnt[j]--;dfs(now,tot+1,pcnt+6);cnt[j]++;}}cnt[i]++;}}for(int i=1;i<=13;++i){    //4 + 2 + 2if(cnt[i]>=2){cnt[i]-=2;for(int j=i;j<=13;++j){if(cnt[j]>=2){cnt[j]-=2;dfs(now,tot+1,pcnt+8);cnt[j]+=2;}}cnt[i]+=2;}}dfs(now,tot+1,pcnt+4);    //4cnt[now]+=4;}if(cnt[now]>=3){cnt[now]-=3;for(int i=now+1;i<=12;++i){  //3 顺子if(cnt[i]<3) break;if(i-now+1>=2){for(int j=now+1;j<=i;++j) cnt[j]-=3;dfs(now,tot+1,pcnt+(i-now+1)*3);for(int j=now+1;j<=i;++j) cnt[j]+=3;}}for(int i=1;i<=15;++i){   //3 + 1if(i==now) continue;if(cnt[i]>=1){cnt[i]--;dfs(now,tot+1,pcnt+4);cnt[i]++;}}for(int i=1;i<=15;++i){   //3 + 1if(i==now) continue;if(cnt[i]>=2){cnt[i]-=2;dfs(now,tot+1,pcnt+5);cnt[i]+=2;}}dfs(now,tot+1,pcnt+3);   //3cnt[now]+=3;}if(cnt[now]>=2){cnt[now]-=2;for(int i=now+1;i<=12;++i){    //2  顺子if(cnt[i]<2) break;if(i-now+1>=3){for(int j=now+1;j<=i;++j) cnt[j]-=2;dfs(now,tot+1,pcnt+(i-now+1)*2);for(int j=now+1;j<=i;++j) cnt[j]+=2;}}for(int i=1;i<=13;++i){       //2 + 4 + 2if(cnt[i]>=4 and i!=now){cnt[i]-=4;for(int j=1;j<=13;++j){if(cnt[j]>=2 and j!=i){cnt[j]-=2;dfs(now,tot+1,pcnt+8);cnt[j]+=2;}}cnt[i]+=4;}}for(int i=1;i<=13;++i){      //2 + 3if(cnt[i]>=3 and i!=now){cnt[i]-=3;dfs(now,tot+1,pcnt+5);cnt[i]+=3;}}dfs(now,tot+1,pcnt+2);cnt[now]+=2;}if(cnt[now]>=1){cnt[now]-=1;for(int i=now+1;i<=12;++i){   //顺子if(cnt[i]<1) break;if(i-now+1>=5){for(int j=now+1;j<=i;++j) cnt[j]--;dfs(now,tot+1,pcnt+(i-now+1));for(int j=now+1;j<=i;++j) cnt[j]++;}}for(int i=1;i<=13;++i){        //1 + 4 + 1if(cnt[i]>=4 and i!=now){cnt[i]-=4;for(int j=1;j<=15;++j){if(cnt[j]>=1 and j!=i){cnt[j]--;dfs(now,tot+1,pcnt+6);cnt[j]++;}}cnt[i]+=4;}}for(int i=1;i<=13;++i){        //1 + 3if(cnt[i]>=3 and i!=now){cnt[i]-=3;dfs(now,tot+1,pcnt+4);cnt[i]+=3;}}dfs(now,tot+1,pcnt+1);cnt[now]++;}
}
signed main(){ios::sync_with_stdio(false);cthin>>t>>n;while(t--){memset(cnt,0,sizeof cnt);ans=0x7fffffff;for(int i=1;i<=n;++i){cthin>>a[i];if(a[i].x!=0){cnt[mp(a[i].x)]++;}else{if(a[i].y==1) xw=1;else dw=1;}}dfs(1,0,0);cout<<ans<<'\n';}
}

Expression

收回说这道题很好的评价

首先我们只考虑当前枚举到的第 \(i\) 位(假设第 \(i\) 位之前都已经满足要求了,此时高位的决策是不会影响第 \(i\) 位的),设 \(a\) 的第 \(i\) 位为 \(a_i\)\(b,c\) 同理,并且从低位进上来的数为 \(j\),容易发现,当 \(a_i+b_i+j=c_i\) 的时候,这一位我们就不需要考虑了,直接去做下一位就行了

对于搜索的结束情况,就是 \(c\) 的更高位已经没有东西了,这个时候你只需要把当前 \(a,b\) 还没处理的高位丢到 \(c\) 的高位上就结束了

那么怎么处理不合法的情况

这个时候你可以考虑在第 \(i\) 位插入一个数字,随便 \(a,b,c\) 哪个数都行,只要能让它变成 \(a_i+b_i+j=c_i\) 的情况即可(这里实际上写的时候应该是在模 \(10\) 意义下,并且注意处理进位),然后你就可以将其转化为上面那种情况然后继续处理下一位了

思路不难,但是写起来很恶心,涉及到往某一位插入,最高位插入,还要统计最终答案的三个数字,建议是全都传到 dfs 里,对于插入的问题可以通过向 dfs 里传一个当前位数来解决

不评价了,难写的要死,只能说不愧是紫搜

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a,b,c;
int ans=0x7fffffff;
int aans[4];
int base10[64];
void dfs(int a,int b,int c,int jw,int na,int nb,int nc,int nowans,int deep){// cout<<"dfs "<<a<<" "<<b<<" "<<c<<" "<<jw<<" "<<na<<" "<<nb<<" "<<nc<<" "<<nowans<<" "<<deep<<endl;if(nowans>ans) return;if(a==0 and b==0 and c==0 and jw==0){if(nowans<ans){ans=nowans;aans[0]=na;aans[1]=nb;aans[2]=nc;}return;}if(c==0){int tmp=(a+b+jw);int res=0;while(tmp){res++;tmp/=10;}dfs(0,0,0,0,na+a*base10[deep],nb+b*base10[deep],nc+base10[deep]*(a+b+jw),nowans+res,deep+1);return;}if((a+b+jw)%10==c%10){dfs(a/10,b/10,c/10,(a%10+b%10+jw)/10,na+(a%10)*base10[deep],nb+(b%10)*base10[deep],nc+(c%10)*base10[deep],nowans,deep+1);}dfs(a*10+(c%10-b%10-jw+10)%10,b,c,jw,na,nb,nc,nowans+1,deep);dfs(a,b*10+(c%10-a%10-jw+10)%10,c,jw,na,nb,nc,nowans+1,deep);dfs(a,b,c*10+(a%10+b%10+jw)%10,jw,na,nb,nc,nowans+1,deep);
}
signed main(){base10[0]=1;for(int i=1;i<=18;++i) base10[i]=base10[i-1]*10;scanf("%lld+%lld=%lld",&a,&b,&c);dfs(a,b,c,0,0,0,0,0,0);printf("%lld+%lld=%lld",aans[0],aans[1],aans[2]);
}

Distinct Paths

一眼:\(N,M\le 1000\),我打暴搜,真的假的?

两眼:DP 套 DFS 吗

三眼:草,诈骗题

注意到路径上的颜色最多只有 \(n+m-1\) 个,如果你 \(k\) 连这个都达不到那就没有合法方案

所以你就可以搜了,因为这个题求的是集合,所以你可以直接搜坐标

因为只能向右/向下走,可以对 \(k\) 状压一下,求出 \((x-1,y),(x,y-1)\) 的决策颜色之和,然后枚举没选过的颜色填进去

优化

  • 如果当前没填的数比要走的格子还少,无解
  • 如果一个数第一次被填,则填什么都是一样的(注意这里 “第一次被填” 指的是没有在之前的决策与已经确定的数中出现过)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=1e9+7;
int n,m,k;
int mp[101][101];
int used[101][101];
int cnt[101];
int dfs(int x,int y){if(x==n+1){return 1;}int res=0;int now=used[x-1][y]|used[x][y-1];if(k-__builtin_popcount(now)<((m-y)+(n-x)+1)) return res;if(mp[x][y]==0){int ls=-1;for(int i=1;i<=k;++i){if((now&(1<<i))==0){cnt[i]++;used[x][y]=now|(1<<i);if(cnt[i]==1 and ls==-1) ls=dfs(y==m?x+1:x,y==m?1:y+1);if(cnt[i]==1) res+=ls;else res+=dfs(y==m?x+1:x,y==m?1:y+1);res%=p;cnt[i]--;}}}else{if((now&(1<<mp[x][y]))==0){used[x][y]=now|(1<<mp[x][y]);res+=dfs(y==m?x+1:x,y==m?1:y+1);res%=p;}}return res%p;
}
signed main(){cin>>n>>m>>k;if(n+m-1>k){cout<<0;return 0;}for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){cin>>mp[i][j];cnt[mp[i][j]]++;}}cout<<dfs(1,1)%p<<endl;
}

Rotation Puzzle

挺板的一道题

折半搜索可以将 \(2^{2k}\) 的复杂度降到 \(2^{k+1}\log\),前提是知道最终状态

从起始状态开始搜一半状态,最后开始搜一半状态,然后从中间合并答案,找合并点用哈希就行了

这题我也不知道卡不卡哈希,我的单进制哈希被卡了,但是双进制单模哈希没被卡

最难的部分应该是旋转怎么写

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int mp[101][101];
int mp2[101][101];
inline void rotate(int x,int y){for(int i=x;i<=x+n-2;++i){for(int j=y;j<=y+m-2;++j){int nx=x+n-2-i+x,ny=y+m-2-j+y;if(i<nx or (i==nx and j<ny)){swap(mp[i][j],mp[nx][ny]);}}}
}
inline void rotate2(int x,int y){for(int i=x;i<=x+n-2;++i){for(int j=y;j<=y+m-2;++j){int nx=x+n-2-i+x,ny=y+m-2-j+y;if(i<nx or (i==nx and j<ny)){swap(mp2[i][j],mp2[nx][ny]);}}}
}
int ans=0x7fffffff;
unsigned long long __hash(){unsigned long long ans=0;for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){ans=ans*(n+m+1)+mp[i][j];}}return ans;
}
unsigned long long __hash2(){unsigned long long ans=0;for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){ans=ans*(n+m+1)+mp2[i][j];}}return ans;
}
unsigned long long __0hash(){unsigned long long ans=0;for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){ans=ans*233+mp[i][j];}}return ans;
}
unsigned long long __0hash2(){unsigned long long ans=0;for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){ans=ans*233+mp2[i][j];}}return ans;
}
map<pair<unsigned long long,unsigned long long>,int>hast;
void dfs(int tot){if(tot>10) return;int tmp=__hash(),tmp2=__0hash();if(!hast.count({tmp,tmp2})) hast[{tmp,tmp2}]=tot;else hast[{tmp,tmp2}]=min(hast[{tmp,tmp2}],tot);rotate(1,1);dfs(tot+1);  //1rotate(1,1);rotate(1,2);dfs(tot+1);  //2rotate(1,2);rotate(2,1);dfs(tot+1);  //3rotate(2,1);rotate(2,2);dfs(tot+1);  //4rotate(2,2);
}
void dfs2(int tot){if(tot>10) return;int tmp=__hash2(),tmp2=__0hash2();if(hast.count({tmp,tmp2})){ans=min(ans,hast[{tmp,tmp2}]+tot);}rotate2(1,1);dfs2(tot+1);  //1rotate2(1,1);rotate2(1,2);dfs2(tot+1);  //2rotate2(1,2);rotate2(2,1);dfs2(tot+1);  //3rotate2(2,1);rotate2(2,2);dfs2(tot+1);  //4rotate2(2,2);
}
signed main(){cin>>n>>m;for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){cin>>mp[i][j];mp2[i][j]=(i-1)*m+j;}}dfs(0);dfs2(0);cout<<(ans>20?-1:ans);
}

Anya and Cubes

还是板子题大佬

不是,你放这么多折半板子干啥

这个题甚至比上一个简单

你对 \(n\) 取一个中点,分别对前一半和后一半搜就行了

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k,s;
int a[30];
int fact(int x){__int128 res=1;for(int i=x;i>=1;--i){res*=i;if(res>s) return -1;}return res;
}
int mid;
map<pair<int,long long>,int>mp;
map<long long,bool>vis;
void dfs1(int now,int factcnt,int sum){if(now>mid){mp[{factcnt,sum}]++;vis[sum]=true;return;}dfs1(now+1,factcnt,sum);dfs1(now+1,factcnt,sum+a[now]);int tmp=fact(a[now]);if(tmp!=-1) dfs1(now+1,factcnt+1,sum+tmp);
}
long long ans=0;
void dfs2(int now,int factcnt,int sum){if(now<=mid){if(vis.count(sum)){for(int l=0;l+factcnt<=k;++l){ans+=mp[{l,sum}];}}return;}dfs2(now-1,factcnt,sum);dfs2(now-1,factcnt,sum-a[now]);int tmp=fact(a[now]);if(tmp!=-1) dfs2(now-1,factcnt+1,sum-tmp);
}
signed main(){cin>>n>>k>>s;for(int i=1;i<=n;++i) cin>>a[i];mid=n/2;dfs1(1,0,0);dfs2(n,0,s);cout<<ans;
}

卖瓜

怎么还是折半板子

没啥可说的

这个题和前两个题的区别是要加剪枝,否则可能会 TLE

所以 cc_hash_table 和 gp_hash_table 区别在哪

点击查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace std;
using namespace __gnu_pbds;
int n,m;
int mid;
int ans=114514,cnt;
int a[32];
long long b[32];
cc_hash_table<int,int>mp;
void dfs1(int now,int pcnt,int sum){if(sum>m) return;if(sum+b[now]<m) return;if(sum==m){mp[sum]=pcnt;return;}if(now==mid+1){if(mp[sum]) mp[sum]=min(mp[sum],pcnt+1);else mp[sum]=pcnt+1;return;}dfs1(now+1,pcnt,sum+a[now]);dfs1(now+1,pcnt+1,sum+a[now]/2);dfs1(now+1,pcnt,sum);
}
void dfs2(int now,int pcnt,int sum){if(sum>m or pcnt>ans) return;if(sum==m){ans=min(ans,mp[sum]+pcnt);return;}if(now==n+1){if(mp[m-sum]) ans=min(ans,pcnt+mp[m-sum]-1);return;}dfs2(now+1,pcnt,sum+a[now]);dfs2(now+1,pcnt+1,sum+a[now]/2);dfs2(now+1,pcnt,sum);
}
int main(){cin>>n>>m;mid=min(n/2+1,n-2);m*=2;for(int i=1;i<=n;i++){cin>>a[i];a[i]*=2;}sort(a+1,a+1+n);for(int i=n;i>=1;i--){b[i]=b[i+1]+a[i];}dfs1(1,0,0);dfs2(mid+1,0,0);if(ans==114514) cout<<-1;else cout<<ans;
}

字符串

ANT-Antisymmetry

好像是什么马拉车板子

哈希秒了

发现神奇等式:马拉车*log=二分+哈希

首先考虑设计两个哈希,一个直接哈,另一个对原串取反之后做逆哈希,这样两个哈希相等就满足题目要求了

发现一个节点可以拓展的区间长度具有连续性(单调性),套个二分就完了

点击查看代码
#include<bits/stdc++.h>
// #include"include/hdk/tool.h"
using namespace std;
string x,rx;
const unsigned long long num=233;
unsigned long long h[500001],rh[500002],basenum[500002];
inline unsigned long long geth(int l,int r){return h[r]-h[l-1]*basenum[r-l+1];
}
inline unsigned long long getrh(int l,int r){return rh[l]-rh[r+1]*basenum[r-l+1];
}
int main(){int n;cin>>n>>x;x="."+x;rx=".";basenum[0]=1;for(int i=1;i<=n;++i){h[i]=h[i-1]*num+x[i];basenum[i]=basenum[i-1]*num;rx.push_back(1-(x[i]-'0')+'0');}for(int i=n;i>=1;--i){rh[i]=rh[i+1]*num+rx[i];}// cout<<rx<<endl;// cout<<geth(2,n)<<" "<<hdk::tool::str_t(x)["2::"]<<" "<<hdk::tool::strhash(hdk::tool::str_t(x)["2::"])<<endl;// cout<<getrh(2,n)<<" "<<hdk::tool::strhash(hdk::tool::str_t(rx)["2::-1"])<<endl;// cout<<hdk::tool::str_t(rx)["2:4:"]<<" "<<geth(2,3)<<" "<<getrh(2,3)<<" "<<hdk::tool::strhash(hdk::tool::str_t(rx)["2:4:"])<<endl;long long ans=0;for(int i=1;i<=n-1;++i){if(x[i]!=x[i+1]){int l=2*i+1-n,r=i,tans=i;while(l<=r){int mid=(l+r)/2;int midr=i*2-mid+1;if(geth(mid,midr)==getrh(mid,midr)){r=mid-1;tans=mid;}else l=mid+1;}ans+=i-tans+1;}}cout<<ans;
}

最长双回文串

好题,赞了

好像还是什么马拉车板子

哈希秒了

发现这个题的实质就是让你找两个回文区间拼起来

找回文区间解决很简单,只需要把每个节点处的回文半径找出来,根据单调性则有小于回文半径的点都合法

然后考虑怎么拼长度最长

考虑两个回文区间 \(i(r=r_i),j(r=r_j)\),然后你只需要让 \(i+r_i-1\gt r-r_j+1\) 就能判断有交,有交了就总能从两个回文区间里挑出两部分拼在一块

为了使区间长度最大,则当钦定 \(i\) 枚举 \(j\) 时,应该让 \(j\) 尽量小

权值线段树可以做到,只需要每次插入 \(i+r_i\),查询时查询 \([i-r_i,+\infty)\) 内的最小下标即可

选了可能是复杂度最高的一种写法,但是我写着很舒服(而且我也不会别的了)

犯了不是很典型的错误,详见

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const unsigned long long num=233;
string xp,x;
unsigned long long h[500001],hr[500002],basenum[500001];
inline unsigned long long geth(int l,int r){return h[r]-h[l-1]*basenum[r-l+1];
}
inline unsigned long long getrh(int l,int r){return hr[l]-hr[r+1]*basenum[r-l+1];
}
namespace stree{struct tree{int l,r;int minn;}t[2000001];#define tol (id*2)#define tor (id*2+1)#define mid(l,r) mid=((l)+(r))/2void build(int id,int l,int r){t[id].l=l;t[id].r=r;if(l==r){t[id].minn=0x7fffffff;return;}int mid(l,r);build(tol,l,mid);build(tor,mid+1,r);t[id].minn=min(t[tol].minn,t[tor].minn);}void change(int id,int pos,int val){if(t[id].l==t[id].r){t[id].minn=min(t[id].minn,val);return;}if(pos<=t[tol].r) change(tol,pos,val);else change(tor,pos,val);t[id].minn=min(t[tol].minn,t[tor].minn);}int ask(int id,int l,int r){if(l<=t[id].l and t[id].r<=r){return t[id].minn;}if(r<=t[tol].r) return ask(tol,l,r);if(l>=t[tor].l) return ask(tor,l,r);return min(ask(tol,l,t[tol].r),ask(tor,t[tor].l,r));}
}
int p[500001];
int main(){cin>>xp;x=".";for(char i:xp){x.push_back(i);x.push_back('#');}x.back()='*';int n=(int)x.length()-2;basenum[0]=1;stree::build(1,1,n);for(int i=1;i<=n;++i){h[i]=h[i-1]*num+x[i];basenum[i]=basenum[i-1]*num;}for(int i=n;i>=1;--i){hr[i]=hr[i+1]*num+x[i];}for(int i=1;i<=n;++i){int l=1,r=i,ans=i;while(l<=r){int mid(l,r);int tmp=2*i-mid;if(!(tmp>=1 and tmp<=n)) l=mid+1;else if(geth(mid,tmp)==getrh(mid,tmp)){r=mid-1;ans=mid;}else l=mid+1;}p[i]=i-ans+1;}int ans=0;for(int i=1;i<=n;++i){int ret=stree::ask(1,i-p[i]+1-((x[i-p[i]]=='#')?1:0),n);if(ret!=0x7fffffff){ans=max(ans,i-ret);}stree::change(1,i+p[i],i);}cout<<ans;
}

MUH and Cube Walls

形状一样这个概念也太模糊了

相当于差分之后做模式串匹配

KMP 即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int a[200001];
int b[200001];
int x[200001],y[200001];
int p[200001];
signed main(){cin>>n>>m;for(int i=1;i<=n;++i){cin>>a[i];if(i!=1) x[i-2]=a[i]-a[i-1];}for(int i=1;i<=m;++i){cin>>b[i];if(i!=1) y[i-2]=b[i]-b[i-1];}int lenx=n-2,leny=m-2;if(m==1){cout<<n<<endl;return 0;}for(int i=1;i<=leny;++i){p[i]=p[i-1];while(y[p[i]]!=y[i] and p[i]!=0){p[i]=p[p[i]-1];}if(y[p[i]]==y[i]) p[i]++;}int j=0,ans=0;for(int i=0;i<=lenx;++i){while(j!=0 and y[j]!=x[i]) j=p[j-1];if(y[j]==x[i]) j++;if(j==leny+1){ans++;j=p[j-1];}}cout<<ans;
}

Obsessive String

好题啊

截取互不相交的子串,可以设当前子串的结束位置为 \(i\),然后设计 \(f_i\) 表示该种情况下的方案数

转移的时候需要尝试枚举当前子串的结束位置,可以预处理一个 \(p_i\) 数组表示当前位置向左最近的与 \(T\) 匹配的字符串位置,然后因为这个子串需要包含至少一个 \(T\) 串,因此对 \([1,p_i]\) 范围内的下标都符合转移要求,这一维转移可以用前缀和优化

此外还有一类转移是不在此处填数,可以直接从任意小于 \(i\) 的下标转移,也可以直接前缀和处理

预处理 \(p_i\) 直接哈希就行了

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const unsigned long long num=233;
const int p=1e9+7;
int n;
string s,t;
unsigned long long basenum[100001],h[100001];
unsigned long long hasht=0;
unsigned long long geth(int l,int r){return h[r]-h[l]*basenum[r-l];
}
int pos[100001],f[100001],sum[100001];
int main(){cin>>s>>t;s="."+s;n=(int)s.length()-1;basenum[0]=1;for(int i=1;i<=n;i++){h[i]=h[i-1]*num+s[i];basenum[i]=basenum[i-1]*num;}for(char i:t) hasht=hasht*num+i;for(int i=1;i<=n;i++){if(i>=(int)t.length() and geth(i-(int)t.length(),i)==hasht) pos[i]=i-(int)t.length()+1;else pos[i]=pos[i-1];}f[0]=sum[0]=1;for(int i=1;i<=n;i++){f[i]=f[i-1];if(pos[i]!=0){f[i]=(f[i]+sum[pos[i]-1])%p;}sum[i]=(sum[i-1]+f[i])%p;}cout<<(f[n]-1+p)%p;
}

Short Code

比较有意思的板

首先,注意到只有前缀相同的才会产生冲突,这启发我们建 trie 树

然后建出 trie 以后,只有在同一个节点子树内结尾的字符串会产生冲突

除了 trie 根节点之外,每个节点都可以放一个字符串

贪心地想,对于每一个非根节点,如果其为空,则应该将子树内离它最远的节点移动到该节点上,这样做答案的减少量是最大的

因此维护子树内字符串结尾的最大深度,转移即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
template<int stand>
struct trie{int to[100001][26];bool exist[100001];priority_queue<int>q[100001];int cnt=0,tot_len=0;void insert(const string&x){int pos=0;for(char i:x){if(to[pos][i-stand]==0){to[pos][i-stand]=++cnt;}pos=to[pos][i-stand];}exist[pos]=true;q[pos].push((int)x.length());tot_len+=(int)x.length();}inline void merge(int x,int y){while(!q[y].empty()){q[x].push(q[y].top());q[y].pop();}}int dfs(int now,int deep){int res=0;for(int i=0;i<=25;++i){if(to[now][i]!=0){res+=dfs(to[now][i],deep+1);merge(now,to[now][i]);}}if(now and (exist[now]==false)){res+=q[now].top()-deep;q[now].pop();q[now].push(deep);}return res;}
};
trie<'a'>t;
string l;
int main(){cin>>n;for(int i=1;i<=n;++i){cin>>l;t.insert(l);}cout<<t.tot_len-t.dfs(0,0);
}

杂题选改

CF2025B Binomial Coefficients, Kind Of

根据题目里给的程序打一下表可以发现,题目里的式子等价于求

\[C_{n,k}\begin{cases}1&{n=k}\\2^k&{\text{otherwise}}\end{cases} \]

直接模拟即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int p=1e9+7;
long long power(long long a,long long t){long long ans=1,base=a;while(t){if(t&1){ans=ans*base%p;}base=base*base%p;t>>=1;}return ans;
}
int n1[100001],k[100001];
int main(){ios::sync_with_stdio(false);int n;cin>>n;for(int i=1;i<=n;++i){cin>>n1[i];}for(int i=1;i<=n;++i){cin>>k[i];}for(int i=1;i<=n;++i){if(n1[i]==k[i]) cout<<1<<'\n';else cout<<power(2,k[i])<<'\n';}
}

CF2030D QED's Favorite Permutation

先说一下结论:你会发现形如 LR 这种东西完全就是不连通的,因此如果 LR 左边有东西需要挪到右边去(或者左边),那么答案一定不合法

只有一种情况是允许存在这个断点的,那就是没有东西需要从这个断点经过

用数学语言表述就是,这个断点以前的所有数字的最大值不大于这个断点的坐标

在实现上我们可以按前缀 \(\max_i=i\) 的点将原序列分成若干连通块,答案合法当且仅当不存在处于同一个连通块内的 LR,到这里就能直接上 Trick 了

不知道第几次见这个 Trick 了,上次 CF (2021C2)也是一种类似的 Trick

就是,如果进行一次修改,对结果的影响并不大的话,你可以直接考虑暴力维护结果,然后每次修改暴力地删除可能受到影响的状态点,再加上新的状态点

这种套路对于判断是否合法的题十分好用,你只需要将不合法的位置丢进什么数据结构维护一下,可以发现只要不合法的点集非空,答案就一定不合法,这样你查询是常数复杂度的,然后修改的时候,注意到修改 \(i\) 只可能更改 \((i-1,i)\)\((i,i+1)\) 的合法状态,因此考虑先在非法集合里删掉,修改完了再判断一下是否要加入非法集合内

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,q;
string s;
int a[200001];
int maxn[200001];
int belong[200002];
set<int>posoflr;
signed main(){ios::sync_with_stdio(false);int cases;cin>>cases;while(cases--){posoflr.clear();cin>>n>>q;for(int i=1;i<=n;++i){cin>>a[i];if(maxn[i-1]==i-1){belong[i]=1;}else belong[i]=0;maxn[i]=max(maxn[i-1],a[i]);belong[i]+=belong[i-1];}cin>>s;s=" "+s;for(int i=1;i<=(int)s.length()-2;++i){if(s[i]=='L' and s[i+1]=='R' and belong[i]==belong[i+1]){posoflr.insert(i);}}while(q--){int pos;cin>>pos;if(s[pos]=='L') s[pos]='R';else s[pos]='L';if(pos!=1){auto iter=posoflr.lower_bound(pos-1);if(iter!=posoflr.end() and *iter==pos-1) posoflr.erase(iter);if(s[pos-1]=='L' and s[pos]=='R' and belong[pos-1]==belong[pos]){posoflr.insert(pos-1);}}if(pos!=n){auto iter2=posoflr.lower_bound(pos);if(iter2!=posoflr.end() and *iter2==pos) posoflr.erase(iter2);if(s[pos]=='L' and s[pos+1]=='R' and belong[pos]==belong[pos+1]){posoflr.insert(pos);}}if(posoflr.empty()) cout<<"Yes\n";else cout<<"No\n";}}
}

CF2023D Many Games

一个比较明显的暴力思路是,如果我们钦定选定的物品的价值,那么可以比较容易地由背包 DP 算出能达到这个钦定值的最大概率,从 \([0,\sum w_i]\) 枚举所有可能的价值,暴力跑若干次背包

一个比较 naive 的优化是,你发现你根本就不用钦定选定价值,直接跑一遍背包就能把所有答案跑出来

此外还有三个优化,第一个优化是对我们枚举上界的优化。推一遍式子,考虑设当前 \(\prod \frac{p_i}{100}=x,\sum w_i=y\),当加入物品 \((p,w)\) 时对答案有正贡献,当且仅当

\[\begin{aligned}xy&\lt x\times \frac{p}{100}\times (y+w)\\y&\lt \frac{p}{100}(y+w)\\(1-\frac{p}{100})y&\lt\frac{p}{100}w\\y&\lt\frac{pw}{100-p}\end{aligned} \]

由于题目规定 \(pw\le 2\times 10^5\),而 \(100-p\) 的下界是 \(1\),因此加入物品 \((p,w)\) 时对答案有正贡献的一个必要条件是 \(\sum\limits w_i\le 2\times 10^5\),这样我们就将答案区间 从 \([0,\sum w_i]\) 降到 \([0,2\times 10^5]\)

第二个优化是,我们贪心地想,选择 \(p_i=100\)\(i\) 一定是最优的,因此我们可以提前将 \(p_i=100\)\(i\) 全部选上,这样既压缩了答案区间也减少了物品数量

第三个优化是,我们观察剩下的 \(p_i\neq 100\) 的物品,如果我们将 \(p_i\) 相同的 \(i\) 分成同一组,并且让每一组内按照 \(w_i\) 降序排列,根据贪心思路,当 \(p\) 相同的时候,应该是 \(w\) 越大越好,最终答案一定是形如从每个 \(p_i\) 的组内选出一个前缀。和刚才的思路类似,现在我们钦定我们选择的物品的 \(p_i\) 都为 \(p\),如果我们给当前 \(p\) 选择的前缀长度从 \(k\) 增加到 \(k+1\),答案更优当且仅当(这里给每个概率挂分母太麻烦了,在下面几个式子里钦定 \(p=\frac{p_i}{100}\)

\[\begin{aligned}p^k\times \sum\limits_k w\lt p^{k+1}\times(w_{k+1}+\sum\limits_k w)\end{aligned} \]

由于新加入的 \(w_{k+1}\) 不大于已有的任何一个数,因此有 \(k\times w_{k+1}\le\sum\limits_k w_i\),因此

\[p^k\times \sum\limits_k w\le p^{k+1}\times\frac{k+1}{k}\times\sum\limits_k w_i \]

\[\frac{k}{k+1}\lt p \]

\[k\lt kp+p \]

\[k\lt \frac{p}{1-p} \]

这个式子意味着只有前 \(\frac{p}{1-p}\) 个物品有可能成为最优解

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int p[200001],w[200001];
struct item{int w,p;
}a[200001];
int cnt=0;
long long orians;
long double f[200001];
long double ans;
vector<int>v[101];
signed main(){cin>>n;for(int i=1;i<=n;++i){cin>>p[i]>>w[i];v[p[i]].push_back(w[i]);if(p[i]==100) orians+=w[i];}for(int i=1;i<=100;++i){sort(v[i].begin(),v[i].end(),greater<int>());}for(int i=1;i<=99;++i){int tot=0;for(int j=0;j<=(int)v[i].size()-1;++j){if(tot>100/(100-i)) break; a[++cnt]={v[i][j],i};tot++;}};f[0]=1;for(int i=1;i<=cnt;++i){for(int j=200000;j>=a[i].w;--j){f[j]=max(f[j],f[j-a[i].w]*a[i].p/100.0);}}for(int i=0;i<=200000;++i){ans=max(ans,f[i]*(orians+i));}printf("%.8Lf",ans);
}

ABC379D Home Garden

这年头,啥题都能控我十分钟了

首先直接维护种树时的全局时间就行了,要求持续时间就和当前做个差

然后需要的是一个数据结构,能够支持查询与删除比特定值大的数字个数

这里比赛的时候唐了,没去分析复杂度,即使你暴力去删除每一个数,复杂度也只有均摊 \(\log\)(每个数只会删除一次)

因此可以考虑直接上 set,每次把需要删除的数全删掉,新增值就是删除前后 set 的大小差

点击查看代码
#define int long long
int q,t,h;
multiset<int>s;
int heved=0,nowt,cnt;
signed main(){ios::sync_with_stdio(false);cin>>q;while(q--){int opt;cin>>opt;if(opt==1){auto iter=s.insert(-nowt);}else if(opt==2){int t;cin>>t;nowt+=t;}else{int h;cin>>h;int tmp=s.size();s.erase(s.lower_bound(-(nowt-h)),s.end());cout<<tmp-(int)s.size()<<'\n';}}
}

ABC379E Sum of All Substrings

这个也是好玩题

首先答案的式子不难发现,应该是一个形如 \(n\times s_{n-1}\times 1+(n-1)\times s_{n-2}\times 11+(n-2)\times s_{n-3}\times 111\cdots\) 之类的东西

写成代码大概这样

import numpy as np
n=int(input())
a=[1]
for i in range(1,n):a.append(a[i-1]*10+1)
s=input()
ans=0
for i in range(n):ans+=(i+1)*a[n-1-i]*int(s[i])
print(ans)

但是问题是这个题没有取模要求,答案又很大,一开始想的是可能考察高精,所以直接去搞 python 了

后来想到 python 玩家又不需要写高精,出题人应该不会出这种题

想到你其实可以把答案拆开,变成一种形如 \(1\times(n\times s_{n-1}+(n-1)\times s_{n-2}\cdots)+10\times(\cdots)+100\times(\cdots)+\cdots\) 之类的东西,拆成这样以后,因为全是十的整次幂,因此可以直接从低到高按位计算了,不需要高精

也是被出题人整了

点击查看代码
n=int(input())
s=input()
a=[(i+1)*int(s[i]) for i in range(n)]
for i in range(1,n):a[i]+=a[i-1]
# sum=[0 for i in range(n)]
# sum=[sum[i-1]+a[i] if i!=0 else a[i] for i in range(n)]
i=0
c=0
ans=[]
while i<n or c>0:if i<n:c+=a[n-1-i]ans.append(c%10)c//=10i+=1
print(*ans[::-1],sep="")

CF1856D More Wrong

昨天开的题

一种思路是小区间合并大区间,昨天在想怎么小区间合并大区间,因为边界问题很难处理,后来看了 DZ 的代码发现其实可分治一样反过来,枚举大区间然后拆分成两个小区间求解,在本层只需要处理这两个最大值哪个比较大就行了

那么怎么求解这两个最大值哪个会比较大

设左,右子区间最大值分别为 \(maxl,maxr\),观察区间 \([maxl,maxr]\),可以发现,如果 \(maxr\) 是最大值的话,将区间从 \([maxl,maxr-1]\) 拓展到 \([maxl,maxr]\) 不会加入任何新的逆序对,因此可以基于这个思想查两次,如果不相同则说明 \(maxr\) 一定不是那个最大值,那么 \(maxl\) 就一定是最大值

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
inline int ask(int l,int r){cout<<"? "<<l<<" "<<r<<endl;int x;cin>>x;return x;
}
int solve(int l,int r){if(l==r) return l;if(r-l==1) return ask(l,r)?l:r;int mid=(l+r)/2;int maxl=solve(l,mid);int maxr=solve(mid+1,r);if(maxr-maxl==1) return ask(maxl,maxr)?maxl:maxr;return (ask(maxl,maxr-1)==ask(maxl,maxr))?maxr:maxl;
}
int main(){int t;cin>>t;while(t--){int n;cin>>n;int x=solve(1,n);cout<<"! "<<x<<endl;}
}

第二种思路是,考虑从 \([1,1]\) 一路拓展到 \([1,n]\),考虑到加入 \(a_i=n\) 的时候,逆序对个数一定不变,并且加入 \(a_i=n\) 以后,无论加入什么元素,逆序对个数总会增加,因此有:最后一个逆序对个数不变的位置为 \(n\) 的位置

但是这个做法爆金币了,显然不够优,套上分治又变成上面那种方法了,因此不赘述了

ABC378E Mod Sigma Problem

什么智慧题

前缀和之后的结果是

\[\sum_{1\le l\le r}((\sum_{l\le i\le r}a_i)\mod m) \]

\[\sum_{1\le l\le r}((sum_r-sum_{l-1})\mod m) \]

到这里开始分讨吧,如果你的 \(sum\) 数组已经事先对 \(m\) 取模了,那么只需要分两种情况讨论:

  • \(sum_r\lt sum_{l-1}\),贡献为 \(sum_r-sum_{l-1}+m\)
  • \(\text{otherwise}\),贡献为 \(sum_r-sum_{l-1}\)

即答案为所有的 \(sum_r-sum_{l-1}\) 加逆序对数量个 \(m\)

随便上个啥维护一下即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/831870.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 提取PowerPoint文档中的图片

如果你需要在多个PowerPoint演示文稿中使用相同的图片,直接从原始PPT中提取并保存图片可以避免重复寻找和下载。此外,将PPT中的重要图片提取出来可以将其作为备份,以防原文件损坏或丢失。本文将通过以下两个示例介绍如何使用Python提取PPT文档中的图片。Python 提取指定幻灯…

高级语言程序设计课程第七次个人作业

班级:https://edu.cnblogs.com/campus/fzu/2024C 作业要求:https://edu.cnblogs.com/campus/fzu/2024C/homework/13304 学号:102400121 姓名:林永庆 12 从左到右,从上到下,从右到左,从下到上345678总结:菜就多练 反思:菜就多练

解线性方程组迭代法

解线性方程组迭代法 在数值分析中,迭代法是解决大规模线性方程组的重要工具。迭代法可以有效地减少计算复杂度,使得求解效率更高。本文将从前置知识开始,介绍向量和矩阵的范数,再深入探讨求解线性方程组的 Jacobi 和 Gauss-Seidel 迭代法。 一、前置知识:向量和矩阵的范数…

Linux kernel 堆溢出利用方法(二)

本文我们通过我们的老朋友heap_bof来讲解Linux kernel中off-by-null的利用手法。在通过讲解另一道相对来说比较困难的kernel off-by-null + docker escape来深入了解这种漏洞的利用手法。前言 本文我们通过我们的老朋友heap_bof来讲解Linux kernel中off-by-null的利用手法。在通…

wpf项目使用winform控件

环境:Win10、VS2017 一、新建WPF项目 2. WPF项目添加System.Windows.Forms和WindowsFormsIntegration引用 3. 编写WPF窗体代码 3.1. 头部添加引用1 xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" 2 xmlns:wfi ="clr-namespac…

Spring带泛型的ApplicationEvent无法监听问题分析(转载)

1 背景 在开发过程中,经常遇到发送事件来通知其他模块进行相应的业务处理;笔者实用的是spring自带的ApplicationEventPublisher和EventListener进行事件的发收; 但是开发时遇到一个问题: 如果事件很多,但是事件模式都差不多,就需要定义很多事件类来分别表示各种事件,例如…

PG 修改表结构提示有试图依赖的处理方法

ALTER TABLE victim ALTER COLUMN victim_belong_url TYPE varchar(1000) USING victim_belong_url::varchar(1000); 修改字段长度 通过修改 pg_attribute 基表的方式来绕开这个限制#通过表名查出attrelid SELECT relname, attname,attnum,attrelid,attname FROM pg_class c,pg…

OMV安装文件管理器filebrowser和照片管理photoprism插件时Pull不了镜像的解决办法

OMV安装文件管理器filebrowser和照片管理photoprism插件安装后不能启动服务或者PULL不了镜像卡着不动都是因为现在国内pull不了镜像的原因 这里有个迷惑的人的地方是很多朋友认为是用docker来pull的镜像,于是增加了docker国内加速源后发现OMV还是拉取不了镜像。解决方法如下:…

NOIP2024加赛4

NOIP2024加赛4\(T1\) luogu P11267 【MX-S5-T1】王国边缘 \(85pts\)预处理前缀中最后一个 \(1\) 出现的位置然后就可以倍增跳了。点击查看代码 const ll p=1000000007; int nxt[200010][62],f[200010][62],last[200010]; char t[200010]; ll divide(ll s,ll k) {ll ans=0;for(l…

触想染织厂MES产线终端工位机,打造数字化高效车间

一、行业发展背景在纺织细分领域中,印染行业一直是整个产业链的效率短板,因其涉及染色、定型及后整理加工等多个复杂工艺、上百个参数变量,质量波动较大,依赖个人经验和手工操作,常常陷入高成本、低效率发展困境。△某印染工厂生产场景二、行业应用需求印染厂要真正实现效…

超强抗干扰单键触摸/电容式触控IC-VK3601 SOT23-6单通道直接输出/触摸感应方案原厂

产品品牌:永嘉微电/VINKA 产品型号:VK3601 封装形式:SOT23-6 概述 VK3601具有1个触摸按键,可用来检测外部触摸按键上人手的触摸动作。该芯片具有较 高的集成度,仅需极少的外部组件便可实现触摸按键的检测。 提供了1路直接输出功能。芯片内部采用特殊的集成电路,具有高电源…

重载和重写的区别

重载(Overloading)和重写(Overriding)是面向对象编程中两个重要的概念,它们在实现多态性时起着关键作用,但两者之间有明显的区别:定义上的区别:重载(Overloading) 指的是在同一个类中可以有多个方法名相同,但这些方法的参数列表(参数的个数、类型或顺序)不同,或者…