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\) 结束的三元上升子序列个数,则不难有转移方程
对于内层的处理直接开一个值域线段树,从前到后在对应值域处插入答案,查询直接插 \(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
根据题目里给的程序打一下表可以发现,题目里的式子等价于求
直接模拟即可
点击查看代码
#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)\) 时对答案有正贡献,当且仅当
由于题目规定 \(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}\))
由于新加入的 \(w_{k+1}\) 不大于已有的任何一个数,因此有 \(k\times w_{k+1}\le\sum\limits_k w_i\),因此
这个式子意味着只有前 \(\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\) 数组已经事先对 \(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\)
随便上个啥维护一下即可