联训题单 / 集训杂题纪要

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))/2
    void 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))/2
    void 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))/2
    void 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 + 1
            if(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 + 2
            if(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);    //4
        cnt[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 + 1
            if(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 + 1
            if(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);   //3
        cnt[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 + 2
            if(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 + 3
            if(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 + 1
            if(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 + 3
            if(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);  //1
    rotate(1,1);
    rotate(1,2);
    dfs(tot+1);  //2
    rotate(1,2);
    rotate(2,1);
    dfs(tot+1);  //3
    rotate(2,1);
    rotate(2,2);
    dfs(tot+1);  //4
    rotate(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);  //1
    rotate2(1,1);
    rotate2(1,2);
    dfs2(tot+1);  //2
    rotate2(1,2);
    rotate2(2,1);
    dfs2(tot+1);  //3
    rotate2(2,1);
    rotate2(2,2);
    dfs2(tot+1);  //4
    rotate2(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))/2
    void 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//=10
    i+=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\)

随便上个啥维护一下即可

posted @ 2024-11-11 17:21  HaneDaniko  阅读(81)  评论(0编辑  收藏  举报