zoukankan      html  css  js  c++  java
  • 10.23

    (revision)货车运输

    • 最大生成树的正确性
    • 图是森林时用并查集维护两点是否相连。
    • dfs(i,i,INT_MAX)赋值为INT_MAX的妙用
    • w[u] [i]表示的时跳到f[u] [i]的沿途边权最小值。既然是求最小值,不要忘了赋初值

    【线段树 带修改最大字段和】 (link)小白逛公园

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    #define rep(i,a,b) for(int i=(a);i<=(b);++i)
    #define dwn(i,a,b) for(int i=(a);i>=(b);--i)
    template <typename T>inline void rd(T &x){
        x=0;char c=getchar();int f=0;
        while(!isdigit(c)){f|=c=='-';c=getchar();}
        while(isdigit(c)){x=x*10+(c^48);c=getchar();}
        x=f?-x:x;
    }
    #define mem(a,b) memset(a,b,sizeof(a))
    #define ee(i,u) for(int i=head[u];i;i=e[i].next)
    /******************************************** Header Template **********************************************/
    #define lson o<<1
    #define rson o<<1|1
    const int N=5e5+10;
    int n,m;
    int val[N];
    
    struct tree{
    	int l,r;
    	int lmax,rmax,sum,maxx;
    }t[N<<2];
    
    inline int max_3(int a,int b,int c){
    	if(a<b)a=b;
    	if(a<c)a=c;
    	return a;
    }
    
    inline void pushup(int o){
    	t[o].sum=t[lson].sum+t[rson].sum;
    	t[o].lmax=max_3(t[lson].lmax,t[lson].sum+t[rson].lmax,t[lson].sum);
    	t[o].rmax=max_3(t[rson].rmax,t[rson].sum+t[lson].rmax,t[rson].sum);
    	t[o].maxx=max_3(t[lson].maxx,t[rson].maxx,t[lson].rmax+t[rson].lmax);
    }
    
    inline void build(int o,int l,int r){
    	t[o].l=l,t[o].r=r;
    	if(l==r){
    		t[o].lmax=t[o].maxx=t[o].rmax=t[o].sum=val[l];
    		return ;
    	}
    	int mid=(l+r)>>1;
    	build(lson,l,mid);
    	build(rson,mid+1,r);
    	pushup(o);
    }
    
    inline void change(int o,int pos,int k){
    	int l=t[o].l,r=t[o].r;
    	if(l==pos && r==pos){
    		t[o].sum=t[o].lmax=t[o].rmax=t[o].maxx=k;
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(pos<=mid)change(lson,pos,k);
    	else change(rson,pos,k);
    	pushup(o);
    }
    
    tree query(int o,int x,int y){
    	int l=t[o].l,r=t[o].r;
    	if(l==x && y==r){
    		return t[o];
    	}
    	int mid=(l+r)>>1;
    	if(y<=mid)return query(lson,x,y);
        else if(mid<x)return query(rson,x,y);
        else{
        	tree ls=query(lson,x,mid);
        	tree rs=query(rson,mid+1,y);
            tree ans;
            ans.maxx=max_3(ls.maxx,rs.maxx,ls.rmax+rs.lmax);
            ans.lmax=max_3(ls.sum,ls.lmax,ls.sum+rs.lmax);
            ans.rmax=max_3(rs.sum,rs.rmax,rs.sum+ls.rmax);
            ans.sum=ls.sum+rs.sum;
            return ans;
        }
    }
    
    #undef int
    int main(){
    #define int long long
    	#ifdef WIN32
    	freopen("a.txt","r",stdin);
    	#endif
    	rd(n),rd(m);
    	rep(i,1,n)rd(val[i]);
    	build(1,1,n);
    	while(m--){
    		int op,x,y,k;
    		rd(op);
    		if(op==1){
    			rd(x),rd(y);
    			if(x>y)swap(x,y);
    			tree ans=query(1,x,y);
                printf("%lld
    ",ans.maxx);
    		}
    		else if(op==2){
    			rd(x),rd(k);
    			change(1,x,k);
    		}
    	}
        return 0;
    }
    /*
    题目描述
    给定长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:
    1、“1 x y”,查询区间 [x,y] 中的最大连续子段和
    2、“2 x y”,把 A[x] 改成 y。
    对于每个查询指令,输出一个整数表示答案。
    第一行两个整数N,M。
    第二行N个整数A[i]。
    接下来M行每行3个整数k,x,y,k=1表示查询(此时如果x>y,请交换x,y),k=2表示修改。
    对于每个查询指令输出一个整数表示答案。
    N≤500000,M≤100000
    N≤500000,M≤100000
    5 3
    1 2 -3 4 5
    1 2 3
    2 2 -1
    1 3 2
    输出样例:
    2
    -1
    sol:
    对于一个固定区间,求最大子段和我们有O(n)
    的算法。但是如何维护不同区间的LIS
    就成了一个问题。我们选择线段树解决区间问题,但使用线段树的话,我们需要明白,维护什么值,以
    及如何进行区间操作。
    
    那么我们思考,对于相邻的两个区间,他们的LIS
    有几种可能?
    1.左侧区间的LIS
    2.右侧区间的LIS
    3.两个区间合并后,中间新连接的部分
    
    前两点都好理解,针对第三点我们继续思考,中间部分能够成为LIS,其实就是我们从连接部分分别向前向后,获
    得一个尽可能大的前/后缀和。那么我们维护或者合并区间的LIS
    就需要维护三个值,区间最大子段和,最大前缀和,最大后缀和。而我们在合并区间的时候,如何维护前/后缀和呢?
    我们需要多维护一个区间和。
    
    整理我们得到,定义区间ls,rs
    合并得到区间d,每个区间维护区间和sum
    ,区间最大字段和maxs
    ,区间最大前缀和maxl
    ,区间最大后缀和maxr
    。则合并区间时,可得关系如下
    d.sum=ls.sum+rs.sum
    d.maxs=max(ls.maxs,rs.maxs,ls.maxr+rs.maxl)
    d.maxl=max(ls.maxl,ls.sum+rs.maxl)
    d.maxr=max(rs.maxr,rs.sum+ls.maxr)
    用线段树维护即可
    */
    

    【欧拉函数】 (link)P4861按钮

    题目要求(K^{x}=1 mod M)的最小x

    不难联想到欧拉定理 (K^{phi(M)} =1mod M)

    • 无解的情况:__gcd(K,M)!=1
    • 引理:x一定是phi(M)的因子,那么枚举phi(M)的因子暴力判断就欧克了。

    时间复杂度:(O(sqrt{M}))的复杂度预处理出phi(M),再用(O(sqrt{phi(M)}))的复杂度枚举因子

    int K,M,ans=INT_MAX;
    
    inline int get_phi(int x){
    	int res=x;
    	for(int i=2;i*i<=x;++i){
    		if(x%i==0)res=res/i*(i-1);
    		while(x%i==0)x/=i;
    	}
    	if(x>1)res=res/x*(x-1);
    	return res;
    }
    
    inline int ksm(int a,int k,int mod){
    	int res=1;
    	for(;k;k>>=1){
    		if(k&1)res=res*a%mod;
    		a=a*a%mod;
    	}
    	return res;
    }
    
    #undef int
    int main(){
    #define int long long
    	#ifdef WIN32
    	freopen("a.txt","r",stdin);
    	#endif
    	rd(M),rd(K);
    	if(__gcd(K,M)!=1){
    		puts("Let's go Blue Jays!");
    		exit(0);
    	}
    	int phi=get_phi(M);
    	for(int i=1;i*i<=phi;i++){
    		if(phi%i==0){
    			if(ksm(K,i,M)==1){
    				ans=min(ans,i);
    			}
    			else if(ksm(K,phi/i,M)==1){
    				ans=min(ans,phi/i);
    			}
    		}
    	}
    	printf("%lld",ans);
        return 0;
    }
    

    【LIS 归约思想】P3902 递增

    转化成已知的问题:

    如果原问题是非严格单调递增,则直接求出 LIS 的⻓度,从 n 中减去就行了。

    注意到 ai 都是整数,ai < (a_{i+1}) 相当于 ai + 1 ≤ (a_{i+1})

    于是

    a1 < a2 < · · · < an−1 < an

    就相当于

    a1 + (n − 1) ≤ a2 + (n − 2) ≤ · · · ≤ (a_n-1)+1 ≤ an

    将原数组进行预处理,就可以转化成 不降LIS 问题了。

    const int N=1e5+10;
    int f[N],a[N];
    int len,n;
    
    int main(){
    	rd(n);
    	rep(i,1,n){
    		rd(a[i]);
    		a[i]+=n-i;
    	}
    	f[++len]=a[1];
    	rep(i,2,n){
    		if(f[len]<a[i])f[++len]=a[i];
    		else{
    			int p=upper_bound(f+1,f+len+1,a[i])-f;
    			f[p]=a[i];
    		}
    	}
    	printf("%d",n-len);
        return 0;
    }
    

    1A【中序遍历 LIS 二叉树 归约思想 】P3365 改造二叉树

    众所周知,二叉搜索树其实就是平衡树,平衡树的中序遍历就是一个严格LIS,遍历二叉树求出这个序列后,就转化成了上一题。

    推导:

    (key[lson]<key[rt]<key[rson])

    --->

    (key[lson]+2 le key[rt]+1 le key[rson])

    那么序列的第i项需要加上的值就是n-i

    那么求出更新后序列的LIS,答案就是n-len.

    const int N=1e5+10;
    int len,n,id;
    int a[N],f[N],val[N];
    int son[N][2];
    
    inline void zhongxu(int u){
    	if(son[u][0])zhongxu(son[u][0]);
    	val[++id]=a[u]+(n-id);
    	if(son[u][1])zhongxu(son[u][1]);
    }
    
    int main(){
    	rd(n);
    	rep(i,1,n)rd(a[i]);
    	rep(i,2,n){
    		int fa,op;
    		rd(fa),rd(op);
    		if(op==0)son[fa][0]=i;
    		else son[fa][1]=i;
    	}
    	zhongxu(1);
    	f[++len]=val[1];
    	rep(i,2,n){
    		if(f[len]<val[i])f[++len]=val[i];
    		else{
    			int p=upper_bound(f+1,f+len+1,val[i])-f;
    			f[p]=val[i];
    		}
    	}
    	printf("%d",n-len);
        return 0;
    }
    

    线性基

    线性基是一种特殊的基,它通常会在异或运算中出现。


    定义

    对于自然数集 S,S 的线性基被定义为最小的集合 R,使得 S 的任何一个子集的 S′,都能找到一个 R 中的子集 R′,S′ 中所有元素的异或和等于 R′ 中元素的异或和,且对于 R 的所有子集 R′,也能找到对应的 S′。

    形象地说,R 内元素随意异或得到的集合等于 S 内元素随意异或得到的集合。


    性质

    1. 线性基能相互异或得到原集合的所有相互异或得到的值。
    2. 线性基是满足性质 1 的最小集合。
    3. 线性基内没有异或和为 0 的子集(因为 R 的空集的异或和为 0)。

    线性组合

    我们从另一个角度来考虑问题,即 x 被表示为其他数的异或和代表什么。我们把 x 拆成一个向量 x={a0,a1,…,ak−1},x=∑2i×ai,其中 k 是位数。

    那么,x 能被表示成 S 的某个子集的异或和 转化为 x 能被表示成 S 中数所对应的向量在模 2 意义下的线性组合。这样一来,R 这个最的小能表示任何数的集合本质上就是 S 的最大线性无关集合

    注意:这里的最小最大并不矛盾,最小意味着线性无关,最大意味着能表示任何一个数

    现在,我们把数的异或,转换成了向量的线性组合


    求解

    高斯消元时,如果某一行被前面的行消为 0,那么代表它可以被表示成前面向量的线性组合,这也就是为什么:

    方程组有解、系数矩阵行列式不为 0、系数矩阵满秩、系数矩阵的线性无关集合大小为 n ——这四个命题等价。

    对于新加入的数 x,如果它能被表示成 R,就代表它能被 R 消成 0。

    我们把向量反过来写,把高位系数写在左边,低位系数写在右边,对所有数做高斯消元。最终矩阵会被消成一个上三角矩阵,剩下的不为 0 的向量就是 R,而且每个向量(数)的最左边的非零数(最高位)都不相同

    因为矩阵行秩等于列秩,所以 R 最多有 k(其中 k 表示位数,一般为 32)个元素。

    由于是在模 2 的意义下,所以我们可以直接压位来做。记 ri 表示最高位为 i 的线性基中的数。每次新加进来一个 x 就从其最高位开始消去,如果某一位(假设为第 i 位)无法消除了就把当前的 x′ 放进 ri 中,即 x′ 在线性基 R 中。

    时间复杂度:O(nlog⁡w),其中 w 为值域。

    #include <cstdio>
    
    const int N = 1e6 + 5;
    
    int n;
    unsigned int a[N], r[32];
    
    void Gauss(int n) {
        for (int i = 1; i <= n; i++) {
            unsigned int x = a[i];
            for (int k = 31; ~k; k--) {
                if (!(x >> k) & 1) continue;
                if (r[k]) {
                    x ^= r[k];
                } else {
                    r[k] = x;
                    break;
                }
            }
        }
    }
    int main() {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf("%u", &a[i]);
        }
        Gauss(n);
        for (int i = 0; i < 32; i++) {
            if (r[i]) printf("%u
    ", r[i]);
        }
        return 0;
    }
    
    /*
    线性基是一种擅长处理异或问题的数据结构.设值域为 [1,N] ,就可以用一个长度为 lceil log_2N 
    的数组来描述一个线性基。特别地,线性基第 i 位上的数二进制下最高位也为第 i 位。
    
    一个线性基满足,对于它所表示的所有数的集合 S , S中任意多个数异或所得的结果均能表示为
    线性基中的元素互相异或的结果,即意,线性基能使用异或运算来表示原数集使用异或运算能表
    示的所有数。运用这个性质,我们可以极大地缩小异或操作所需的查询次数。
    
    插入和判断
    我们考虑插入的操作,令插入的数为 x ,考虑 x 的二进制最高位 i ,
    
    若线性基的第 i 位为 00 ,则直接在该位插入 x ,退出;
    若线性基的第 i位已经有值 a[i],则x=x⊕a[i],重复以上操作直到 x=0.
    如果退出时 x=0,则此时线性基已经可以表示原先的 x了;反之,则说明为了表示 x ,往线性基中加
    入了一个新元素。
    
    很容易证明这样复杂度为log_2也可以用这种方法判断能否通过原数列异或得到一个数 x 。
    
    void insert(int x){
        for(reg int i=I;~i;i--)
            if(x&(1ll<<i))
                if(!a[i]){a[i]=x;return;}
                else x^=a[i];
        flag=true;
    }
    bool check(int x){
        for(reg int i=I;~i;i--)
            if(x&(1ll<<i))
                if(!a[i])return false;
                else x^=a[i];
        return true;
    }
    查询异或最值
    
    查询最小值相对比较简单。考虑插入的过程,因为每一次跳转操作, x 的二进制最高位必定单调降低
    ,所以不可能插入两个二进制最高位相同的数。而此时,线性基中最小值异或上其他数,必定会增大。
    所以,直接输出线性基中的最小值即可。
    
    考虑异或最大值,从高到低遍历线性基,考虑到第 i位时,如果当前的答案 x第 i位为 0,就
    将 x异或上a[i]则不做任何操作。显然,每次操作后答案不会变劣,最终的 x 即为答案。
    
    同样,我们考虑对于一个数 x,它与原数列中的数异或的最值如何获得。用与序列异或最大值类似
    的贪心即可解决。
    
    查询 k小值
    我们考虑进一步简化线性基。显然,一个线性基肯定可以表示为若干个形如 2^i的数。从高到低处理线
    性基每一位,对于每一位向后扫,如果当前数第 i 位为 0,且线性基第 i 位不为0 ,则将当前数异或
    上 a[i]这一操作可以在 O(n^2的时间内解决。
    
    经过这一步操作后,设线性基内共有 cnt个数,则它们共可以表示出 2^cnt个数。当然,对于0 必须特
    殊考虑。如果插入的总数 n与 cnt相等,就无法表示 0了。
    
    同样,考虑最小值时,也必须要考虑到 00 的情况。事实上,如果插入时出现了未被加入的元素,
    就肯定可以表示出 0。随后,我们考虑将 k二进制拆分,用与快速幂类似的方法就可以求出第 k大值。
    
    学过线性代数的同学应该可以看出,这个过程就是对一个矩阵求解异或意义下的秩的过程。因此, cnt leq lceil log_2N 
    ceilcnt≤?log 
    2
    ?	 N? 一定成立。而最终,线性基中保存的也是异或意义下的一组极小线性无关组。
    
    同样,有关线性基的一切运算都可以看做矩阵的初等行列变换,也就可以将其看做线性规划问题。
    同样,可以离线使用高斯消元来构造极小线性基。
    
    bool flag;//可以表示0
    int qmax(int res=0){
        for(reg int i=I;~i;i--)
            res=max(res,res^a[i]);
        return res;
    }
    int qmin(int res=0){
        if(flag)return 0;
        for(reg int i=0;i<=I;i++)
            if(a[i])return a[i];
    }
    int query(int k){
        reg int res=0;reg int cnt=0;
        k-=flag;if(!k)return 0;
        for(reg int i=0;i<=I;i++){
            for(int j=i-1;~j;j--)
                if(a[i]&(1ll<<j))a[i]^=a[j];
            if(a[i])tmp[cnt++]=a[i];
        }
        if(k>=(1ll<<cnt))return -1;
        for(reg int i=0;i<cnt;i++)
            if(k&(1ll<<i))res^=tmp[i];
        return res;
    }
    代码*//*
    reference:
    	
    translation:
    	
    solution:
    
    trigger:
    	
    note:
    	*
    date:
    	2019.08.03
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    #define rep(i,a,b) for(int i=a;i<=b;++i)
    #define dwn(i,a,b) for(int i=a;i>=b;--i) 
    template <typename T> void rd(T &x){x=0;char c=getchar();int f=0;while(!isdigit(c)){f|=c=='-';c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}x=f?-x:x;} 
    #define mem(a,b) memset(a,b,sizeof(a))
    #define reg register
    
    const int I=60;
    int a[61],tmp[61];
    int n,x;
    bool flag;
    
    inline void ins(int x){
    	dwn(i,I,0)
            if(x&(1ll<<i))
                if(!a[i]){a[i]=x;return;}
                else x^=a[i];
        flag=true;
    }
    
    inline bool check(int x){
        for(int i=I;~i;i--)
            if(x&(1ll<<i))
                if(!a[i])return false;
                else x^=a[i];
        return true;
    }
    
    inline int qmax(int res=0){
        dwn(i,I,0)
            res=max(res,res^a[i]);
        return res;
    }
    
    inline int qmin(){
        if(flag)return 0;
        rep(i,0,I)
            if(a[i])return a[i];
    }
    
    inline int query(int k){
        int res=0,cnt=0;
        k-=flag;
    	if(!k)return 0;
        rep(i,0,I){
        	dwn(j,i-1,0)
                if(a[i]&(1ll<<j))a[i]^=a[j];
            if(a[i])tmp[cnt++]=a[i];
        }
        if(k>=(1ll<<cnt))return -1;
        rep(i,0,cnt-1)
            if(k&(1<<i))res^=tmp[i];
        return res;
    }
    
    #undef int
    int main(){
    #define int long long
    	#ifdef WIN32
    	freopen("xxj.txt","r",stdin);
    	#endif
    	rd(n);
    	rep(i,1,n){
    		rd(x);
    		ins(x);
    	}
        printf("%lld
    ",qmax());
        return 0;
    }
    

    BSGS 用于解决高次同余方程的最小整数解,主要思想是分块。


    引入

    求出下面方程的最小非负整数解 x:

    ax≡b(modp)(gcd(a,p)=1)

    此处的未知数 x 在指数上,我们就要用到 BSGS(大步小步法,即 Baby Step Giant Step)算法。


    思想

    BSGS 的本质是分块的思想,我们设 m=⌈p⌉(注意是上取整),又设 x=i×m−j,其中 0≤i≤m,0≤j<m;我们可以将原方程转化为:

    ai×m−j≡b(modp)

    在 x=i×m−j 中,i 即为大步,j 即为小步;我们将 j 移到方程的右边得到:

    ai×m≡b×aj(modp)

    观察到 j 只有 O(m) 种取值,所以我们对于每个 j,把 b×ajmodp 的值记录下来,存放在哈希表中(有没有一种分块打表的感觉?)。然后枚举左半边的 i,将对应的值在哈希表中查找对应的 j,如果能找到对应的 j 那么说明已经找到答案了。

    对于插入哈希表中的 j 的值,显然在同一个值时,j 的值越大越好,那么直接覆盖即可,无需关注细节问题!

    注意:代码中必须要判断无解情况!(本来就无解,或者没有找到合法解)

    时间复杂度:O(p)

    最长的等差子串的长度

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int T,n,m,len,a[20];
    ll l,r,dp[20][15][25][25][20];
    ll dfs(int pos,int pre,ll st,ll sum,int d,int lead,int limit)
    //pos搜到的位置
    //pre前一位数
    //st当前公差最大差值
    //sum整个数字的最大价值
    //d共差
    //lead判断是否有前导0
    //limit判断是否有最高位限制
    {
        if(pos>len) return sum;//dp结束 
        //记录状态(计划搜索)
        //注意d有负数,最小可以到-9,所以记录时数组下标是d+10 
        if((dp[pos][pre][st][sum][d+10]!=-1&&(!limit)&&(!lead))) return dp[pos][pre][st][sum][d+10]; 
        ll ret=0;
        int res=limit?a[len-pos+1]:9;//最高位最大值 
        for(int i=0;i<=res;i++)
        {
            //有前导0且这位也是前导0,一切不变只有位数变化 
            if((!i)&&lead) ret+=dfs(pos+1,0,0,0,-38,1,limit&&(i==res));
            //有前导0但这位不是前导0(这位是数字的最高位)开始有前一位,一个数形成等差数列 
            else if(i&&lead) ret+=dfs(pos+1,i,1,1,-38,0,limit&&(i==res));
            //之前刚搜到1位还没有共差,两位数形成等差数列,记录共差 
            else if(d<-9) ret+=dfs(pos+1,i,2ll,2ll,i-pre,0,limit&&(i==res));
            //搜到2位以后,共差若与前两位相同当前等差数列长度增加,若公差变化则更新整个数字的最大价值,等差数列长度又变为2 
            else if(d>=-9) ret+=dfs(pos+1,i,(i-pre==d)?st+1:2,max(sum,(i-pre==d)?st+1:2),(i-pre==d)?d:i-pre,0,limit&&(i==res));
        }
        //没有前导0和最高限制时可以直接记录当前dp值以便下次搜到同样的情况可以直接使用。 
        return (!limit&&!lead)?dp[pos][pre][st][sum][d+10]=ret:ret;
    }
    ll part(ll x)
    {
        len=0;
        while(x) a[++len]=x%10,x/=10;
        memset(dp,-1,sizeof dp);
        return dfs(1,0,0,0,-38,1,1);//-38是随便赋的其实赋成-10就行了…… 
    }
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%lld%lld",&l,&r);
            //l是0的时候要特别注意!
            printf("%lld
    ",l?(part(r)-part(l-1)):(part(r)-part(l)+1));
        }
        return 0;
    }
    

    P1439 【模板】最长公共子序列

    把 P1 重新标号为 1 到 n ,P2 进行相应的重标号。 那么 P1 的任意一个子序列都是一个单增序列,且任意一个单增序列都 是 P1 的子序列。 那么只需要对重标号后的 P2 求最长上升子序列即可(因为这样的子序列一定是P1的子序列)。

    const int N=1e5+10;
    int a[N],b[N],f[N],len;
    map<int,int>mp;
    int n;
    
    int main(){
    	rd(n);
    	rep(i,1,n){
    		rd(a[i]);
    		mp[a[i]]=i;
    	}
    	rep(i,1,n){
    		int x;rd(x);
    		b[i]=mp[x];
    	}
    	f[++len]=b[1];
    	rep(i,2,n){
    		if(f[len]<b[i])f[++len]=b[i];
    		else{
    			int p=lower_bound(f+1,f+len+1,b[i])-f;
    			f[p]=b[i];
    		}
    	}
    	printf("%d",len);
        return 0;
    }
    

    P4290 [HAOI2008] 玩具取名

    f[l][r][op]区间l->r可以用op表示出来 目标是求 f 1,n,op ,其中 op ∈ {W, I, N, G} 。

    按区间长度从小到大的顺序进行 dp。

    对于 f l,r,op ,枚举第一个字母(也 就是 op )变形成的两个字母是什么,及分别对应 [l,r] 中的哪些位置, 即可利用之前的 dp 信息求出。

    const int N=210;
    bool f[N][N][5];//f[l][r][op]区间l->可以用op表示出来
    bool G[5][5][5];//G[i][j][k] 字符i 和 字符j 可以拼出字符 k
    int num[N];
    int n;
    char s[N];
    map<char,int>mp;
    
    int main() {
    	#ifdef WIN32
    	freopen("a.txt","r",stdin);
    	#endif
    	mp['W']=1,mp['I']=2,mp['N']=3,mp['G']=4;
    	rep(i,1,4)rd(num[i]);
    	rep(i,1,4)
    		rep(j,1,num[i]){
    			char a,b;cin>>a>>b;
    			G[mp[a]][mp[b]][i]=1;
    		}
    	scanf("%s",s+1);
    	n=strlen(s+1);
    	rep(i,1,n)f[i][i][mp[s[i]]]=1;
    	rep(len,2,n)
    		rep(l,1,n-len+1){
    			int r=l+len-1;
    			rep(k,l,r-1)//枚举断点
    				rep(z,1,4)
    					rep(z1,1,4)
    						rep(z2,1,4)
    							f[l][r][z] |= G[z1][z2][z] & f[l][k][z1] &f[k+1][r][z2];
    		}
    	bool flag=0;
    	if(f[1][n][1]){flag=1;printf("W");}
    	if(f[1][n][2]){flag=1;printf("I");}
    	if(f[1][n][3]){flag=1;printf("N");}
    	if(f[1][n][4]){flag=1;printf("G");}
    	if(!flag)puts("The name is wrong!");
        return 0;
    }
    
    
  • 相关阅读:
    了解什么是版本控制 & 用tortoiseSVN 建立本地版本库来管理自己的代码
    windows下用wampServer 为wordpress 搭建本地服务器运行环境
    使用Windows Live Writer 作为博客园PC客户端发表博客
    重装SQL前,一定要把SQL2005、SQL2008之类的彻底删除干净
    jdk-7u40-windows-i586的安装
    【react】---Immutable的基本使用
    【巷子】---webpack配置非CMD规范的模块
    【巷子】---Mock---基本使用
    【巷子】---fetch---基本使用
    【巷子】---vue基于mint-ui三级联动---【vue】
  • 原文地址:https://www.cnblogs.com/sjsjsj-minus-Si/p/11785198.html
Copyright © 2011-2022 走看看