zoukankan      html  css  js  c++  java
  • 近年NOI题目总结

    NOI2015D1T1

    题目大意:$T$ 组数据。在一个程序中有无数个变量 $x_i$。现在有 $n$ 条限制,形如 $x_i=x_j$ 或者 $x_i e x_j$。(对于每个限制 $i,j$ 给定)问是否存在一种合法的赋值方案满足所有限制。

    $1le Tle 10,1le nle 10^5,1le i,jle 10^9$。

    普及难度。先把所有编号离散化,然后对于每个相等的限制,把这两个变量塞到一个集合里(并查集)。最后对于每个不等的限制,判断两个变量是否在一个集合里。

    时间复杂度 $O(Tnlog n)$。

    #include<bits/stdc++.h>
    using namespace std;
    int t,n,u[100010],v[100010],op[100010],tmp[200020],fa[200020];
    int getfa(int x){
        return fa[x]==x?x:fa[x]=getfa(fa[x]);
    }
    void comb(int u,int v){
        int fu=getfa(u),fv=getfa(v);
        if(fu!=fv) fa[fu]=fv;
    }
    bool same(int u,int v){
        int fu=getfa(u),fv=getfa(v);
        return fu==fv;
    }
    int main(){
        scanf("%d",&t);
        while(t--){
            memset(u,0,sizeof(u));
            memset(v,0,sizeof(v));
            memset(op,0,sizeof(op));
            memset(tmp,0,sizeof(tmp));
            scanf("%d",&n);
            for(int i=1;i<=2*n;i++) fa[i]=i;
            for(int i=1;i<=n;i++){
                scanf("%d%d%d",u+i,v+i,op+i);
                tmp[i*2-1]=u[i];
                tmp[i*2]=v[i];
            }
            sort(tmp+1,tmp+2*n+1);
            unique(tmp+1,tmp+2*n+1);
            bool flag=true;
            for(int i=1;i<=n;i++){
                u[i]=lower_bound(tmp+1,tmp+2*n+1,u[i])-tmp;
                v[i]=lower_bound(tmp+1,tmp+2*n+1,v[i])-tmp;
            }
            for(int i=1;i<=n;i++)
                if(op[i]==1) comb(u[i],v[i]);
            for(int i=1;i<=n;i++)
                if(op[i]==0)
                    if(same(u[i],v[i])){
                        flag=false;break;
                    }
            if(flag) printf("YES
    ");
            else printf("NO
    ");
        }
    }
    View Code

    NOI2015D1T2

    题目大意:一棵 $n$ 个点的树,点编号从 $0$ 到 $n-1$,$0$ 为根。一开始每个点点权均为 $0$。接下来有 $q$ 个操作:

    • $ ext{install} u$ 表示将 $u$ 到根的路径上的点点权都变为 $1$;
    • $ ext{uninstall} u$ 表示将 $u$ 子树中所有点点权都变为 $0$。

    每次操作完后,询问有多少个点的点权在这次操作中发生了变化。

    $1le n,qle 10^5$。

    树剖裸题。时间复杂度 $O(n+qlog^2n)$。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int maxn=100010;
    struct edge{
        int to,nxt;
    }e[maxn];
    int n,q,el,dfn,head[maxn];
    int dep[maxn],size[maxn],son[maxn],fa[maxn];
    int id[maxn],w[maxn],top[maxn];
    int sum[maxn<<2],set[maxn<<2];
    inline void add(int u,int v){
        e[++el]=(edge){v,head[u]};
        head[u]=el;
    }
    void dfs1(int u,int f,int d){
        dep[u]=d;
        fa[u]=f;
        size[u]=1;
        int maxson=-1;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            dfs1(v,u,d+1);
            size[u]+=size[v];
            if(size[v]>maxson){
                maxson=size[v];son[u]=v;
            }
        }
    }
    void dfs2(int u,int topf){
        id[u]=++dfn;
        top[u]=topf;
        if(!son[u]) return;
        dfs2(son[u],topf);
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(v==son[u]) continue;
            dfs2(v,v);
        }
    }
    inline void pushup(int t){
        sum[t]=sum[t<<1]+sum[t<<1|1];
    }
    inline void pushdown(int l,int r,int t){
        if(~set[t]){
            int mid=l+r>>1;
            set[t<<1]=set[t];
            set[t<<1|1]=set[t];
            sum[t<<1]=set[t]*(mid-l+1);
            sum[t<<1|1]=set[t]*(r-mid);
            set[t]=-1;
        }
    }
    void build(int l,int r,int t){
        if(l==r){
            set[t]=-1;return;
        }
        int mid=l+r>>1;
        build(l,mid,t<<1);
        build(mid+1,r,t<<1|1);
    }
    void setstate(int L,int R,int l,int r,int x,int t){
        if(L>=l && R<=r){
            set[t]=x;sum[t]=x*(R-L+1);return;
        }
        pushdown(L,R,t);
        int mid=L+R>>1;
        if(mid>=l) setstate(L,mid,l,r,x,t<<1);
        if(mid<r) setstate(mid+1,R,l,r,x,t<<1|1);
        pushup(t);
    }
    int getroot(){
        pushdown(1,n,1);
        return sum[1];
    }
    int install(int u){
        int pre=getroot();
        while(u){
            setstate(1,n,id[top[u]],id[u],1,1);
            u=fa[top[u]];
        }
        return getroot()-pre;
    }
    int uninstall(int u){
        int pre=getroot();
        setstate(1,n,id[u],id[u]+size[u]-1,0,1);
        return pre-getroot();
    }
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n-1;i++){
            int x;
            scanf("%d",&x);
            add(x+1,i+1);
        }
        dfs1(1,0,1);dfs2(1,1);
        build(1,n,1);
        scanf("%d",&q);
        for(int i=1;i<=q;i++){
            char str[10];int x;
            scanf("%s%d",str,&x);x++;
            if(str[0]=='i') printf("%d
    ",install(x));
            else printf("%d
    ",uninstall(x));
        }
    }
    View Code

    NOI2015D2T1

    题目大意:有 $n$ 个字符串,第 $i$ 个在文章中出现了 $w_i$ 次。现在要把每个字符串替换成一个 $k$ 进制字符串(每个字符都是 $0$ 到 $k-1$ 的整数)。假设第 $i$ 个字符串被替换成了 $s_i$,那么要求对于任意 $i e j$ 都有 $s_i$ 不是 $s_j$ 的前缀。现在请求出替换后文章最小的长度($sum w_i|s_i|$),在此基础上求出 $max(|s_i|)$ 的最小值。

    $1le nle 10^5,2le kle 9,0le w_ile 10^{11}$。

    实际上就是哈夫曼树的定义。那么求个 $k$ 进制哈夫曼树即可。

    时间复杂度 $O(k+nlog n)$。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=100010;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    inline ll read(){
        char ch=getchar();ll x=0,f=0;
        while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
        while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
        return f?-x:x;
    }
    int n,k;ll ans;
    struct hhh{
        ll w;int h;
        bool operator<(const hhh &h)const{
            if(w!=h.w) return w>h.w;
            return this->h>h.h;
        }
    };
    priority_queue<hhh> pq;
    int main(){
        n=read();k=read();
        FOR(i,1,n) pq.push((hhh){read(),1});
        if((n-1)%(k-1)!=0) FOR(i,1,k-1-(n-1)%(k-1)) pq.push((hhh){0,1});
        while(pq.size()!=1){
            ll sum=0;int hei=0;
            FOR(i,1,k){
                hhh h=pq.top();pq.pop();
                sum+=h.w;hei=max(hei,h.h);
            }
            pq.push((hhh){sum,hei+1});ans+=sum;
        }
        printf("%lld
    %d
    ",ans,pq.top().h-1);
    }
    View Code

    NOI2015D2T2

    题目大意:有一个长度为 $n$ 的小写字母字符串 $s$,第 $i$ 个字符有权值 $a_i$。对于这个字符串的任意两个不同后缀 $p,q$,定义 $lcp(p,q)$ 为两个后缀的最长公共前缀的长度。现在对于每个 $0le ile n-1$,求出对于所有 $lcp(p,q)ge i$ 的 $p,q$,$a_p imes a_q$ 的和和最大值。

    $1le nle 3 imes 10^5,|a_i|le 10^9$。

    之前没过的又臭又长又慢的做法的题解

    (想看并查集做法的看别人的题解吧……)

    NOI2016D1T1

    题目大意:如果一个字符串可以被拆分为 $AABB$ 的形式,其中 $A$ 和 $B$ 是任意非空字符串,则我们称该字符串的这种拆分是优秀的。现在给出一个长度为 $n$ 的小写字母字符串 $S$,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。$T$ 组数据。

    $1le nle 30000,1le Tle 10$。

    考虑计算以 $i$ 结尾的 $AA$ 有多少个(设为 $a_i$),以 $i$ 开头的 $AA$ 有多少个(设为 $b_i$),答案即为 $sum a_ib_{i+1}$。

    下面以求 $a_i$ 为例。枚举 $A$ 的长度 $l$,然后考虑 $l,2l,3ldotslfloorfrac{n}{l} floor l$ 这些位置。

    对于 $il$ 和 $(i+1)l$,求出这两个后缀的最长公共前缀和这两个前缀的最长公共后缀,设他们的长度分别为 $x$ 和 $y$。(与 $l$ 取最小值)

    (从题解偷张图)

    那么会发现,$x+y<l$ 时,红色荧光笔部分是无法匹配的,所以不存在满足要求的 $AA$ 串。

    而当 $x+yge l$ 时:

    发现粉色和棕色的都是合法的 $AA$ 串。也就是说会对绿色荧光笔部分的每一个 $a$ 都产生 $1$ 的贡献。(这个区间是 $[(i+1)l-y+l-1,(i+1)l+x-1]$)

    $b$ 同理。

    求 $x,y$ 可以用后缀数组做到 $O(1)$,区间加可以用差分做到 $O(1)$。

    总时间复杂度 $O(Tnlog n)$。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=30030;
    #define PB push_back
    #define MP make_pair
    #define lson o<<1,l,mid
    #define rson o<<1|1,mid+1,r
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    inline int read(){
        char ch=getchar();int x=0,f=0;
        while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
        while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
        return f?-x:x;
    }
    struct Suffix_Array{
        char s[maxn];
        int n,m,cnt[maxn],sa[maxn],rak[maxn],tmp[maxn],h[maxn][17],logt[maxn];
        void radix_sort(){
            MEM(cnt,0);
            FOR(i,1,n) cnt[rak[tmp[i]]]++;
            FOR(i,1,m) cnt[i]+=cnt[i-1];
            ROF(i,n,1) sa[cnt[rak[tmp[i]]]--]=tmp[i];
        }
        void build(char s[]){
            MEM(sa,0);MEM(rak,0);MEM(tmp,0);MEM(h,0);
            n=strlen(s+1);m=26;
            FOR(i,1,n) rak[tmp[i]=i]=s[i]-'a'+1;
            radix_sort();
            for(int d=1,p=1;p<n;m=p,d<<=1){
                p=0;
                FOR(i,1,d) tmp[++p]=n-d+i;
                FOR(i,1,n) if(sa[i]>d) tmp[++p]=sa[i]-d;
                radix_sort();swap(rak,tmp);
                rak[sa[1]]=p=1;
                FOR(i,2,n) rak[sa[i]]=(tmp[sa[i]]==tmp[sa[i-1]] && tmp[sa[i]+d]==tmp[sa[i-1]+d])?p:++p;
            }
            int k=0;
            FOR(i,1,n){
                if(k) k--;
                for(int j=sa[rak[i]-1];s[i+k]==s[j+k];k++);
                h[rak[i]][0]=k;
            }
            logt[1]=0;FOR(i,2,n) logt[i]=logt[i>>1]+1;
            FOR(j,1,logt[n]+1) FOR(i,1,n-(1<<j)+1) h[i][j]=min(h[i][j-1],h[i+(1<<j-1)][j-1]);
        }
        int LCP(int x,int y){
            x=rak[x];y=rak[y];
            if(x>y) swap(x,y);
            x++;
            int k=logt[y-x+1];
            return min(h[x][k],h[y-(1<<k)+1][k]);
        }
    }nor,rev;
    int n;
    char str[maxn];
    ll ans,a[maxn],b[maxn];
    int main(){
        for(int t=read();t;t--){
            scanf("%s",str+1);n=strlen(str+1);
            nor.build(str);
            for(int i=1,j=n;i<j;i++,j--) swap(str[i],str[j]);
            rev.build(str);
            MEM(a,0);MEM(b,0);ans=0;
            FOR(l,1,n/2){
                for(int i=l,j=l<<1;j<=n;i+=l,j+=l){
                    int x=min(l,nor.LCP(i,j)),y=min(l-1,rev.LCP(n-i+2,n-j+2));
                    if(x+y>=l){
                        a[j+x-(x+y-l+1)]++;a[j+x]--;
                        b[i-y+(x+y-l+1)]--;b[i-y]++;
                    }
                }
            }
            FOR(i,1,n) a[i]+=a[i-1],b[i]+=b[i-1];
            FOR(i,1,n-1) ans+=a[i]*b[i+1];
            printf("%lld
    ",ans);
        }
    }
    View Code

    NOI2016D2T1

    题目大意:有 $n$ 个区间 $[l_i,r_i]$。现在你要选出 $m$ 个区间,使得至少有一个整点被所有的这些区间覆盖到。对于一个选取方案价值是所有区间的最大长度与最小长度的差。问最小价值。如果没有合法选取方案输出 $-1$。

    $1le nle 5 imes 10^5,1le mle 2 imes 10^5,0le l_ile r_ile 10^9$。

    首先把区间按长度从小到大排序。枚举最短的区间,然后找最长的区间,使得至少有一个点被覆盖至少 $m$ 次(然后就能从里面选出这 $m$ 个区间,是符合要求的)。想让价值最小,就是找到最前面的最长区间。发现这个区间不降,所以可以尺取法。

    至少一个点覆盖 $m$ 次,还有区间加操作,可以变成求区间最大值的线段树。注意在线段树上操作最好先离散化。

    时间复杂度 $O(nlog n)$。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=500050,maxm=200020;
    int n,m,sz,tmp[maxn*2];
    int cnt,rt,mx[maxn*4],add[maxn*4],ch[maxn*4][2];
    struct interval{
        int l,r,len;
        bool operator<(const interval &i)const{return len<i.len;}
    }seg[maxn];
    inline int binary(int x){return lower_bound(tmp+1,tmp+sz+1,x)-tmp;}
    inline void pushup(int x){mx[x]=max(mx[ch[x][0]],mx[ch[x][1]]);}
    inline void pushdown(int x){
        if(add[x]){
            add[ch[x][0]]+=add[x];add[ch[x][1]]+=add[x];
            mx[ch[x][0]]+=add[x];mx[ch[x][1]]+=add[x];
            add[x]=0;
        }
    }
    void build(int &x,int l,int r){
        x=++cnt;if(l==r) return;
        int mid=(l+r)>>1;
        build(ch[x][0],l,mid);build(ch[x][1],mid+1,r);
    }
    void update(int x,int l,int r,int ql,int qr,int v){
        if(l>=ql && r<=qr) return void((add[x]+=v,mx[x]+=v));
        int mid=(l+r)>>1;
        pushdown(x);
        if(mid>=ql) update(ch[x][0],l,mid,ql,qr,v);
        if(mid<qr) update(ch[x][1],mid+1,r,ql,qr,v);
        pushup(x);
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d%d",tmp+i*2-1,tmp+i*2);
            seg[i]=(interval){tmp[i*2-1],tmp[i*2],tmp[i*2]-tmp[i*2-1]};
        }
        sort(tmp+1,tmp+2*n+1);sort(seg+1,seg+n+1);
        build(rt,1,sz=unique(tmp+1,tmp+2*n+1)-tmp-1);
        for(int i=1;i<=n;i++) seg[i].l=binary(seg[i].l),seg[i].r=binary(seg[i].r);
        int cl=1,ans=INT_MAX;
        for(int i=1;i<=n;i++){
            update(rt,1,sz,seg[i].l,seg[i].r,1);
            while(cl<=i && mx[rt]>=m){
                ans=min(ans,seg[i].len-seg[cl].len);
                update(rt,1,sz,seg[cl].l,seg[cl].r,-1);
                cl++;
            }
        }
        printf("%d
    ",ans==INT_MAX?-1:ans);
    }
    View Code

    NOI2017D1T1

    题目大意:有一个大整数 $x$,一开始是 $0$。一共有 $n$ 个操作,有两种操作:

    • $1 a b$ 表示将 $x$ 加上 $a imes 2^b$
    • $2 k$ 表示询问 $x$ 的二进制表示下的第 $k$ 位

    $1le nle 10^6,|a|le 10^9,0le b,kle 3 imes 10^7$,任意时刻 $xge 0$。

    先考虑修改操作。

    有一个结论:如果每次都加正数(以下把加正数叫加,加负数叫减),那么每次暴力进位复杂度均摊 $O(1)$。

    然而这是只有加操作,再有减操作时,就不能暴力进退位了。

    那么可以考虑记录两个数(记为 $add$ 和 $sub$),分别表示加了多少和减了多少,目前每一次操作均摊 $O(1)$。

    再考虑询问操作。

    发现当有一位发生退位时,那么从这位的高一位开始,一整段连续的 $0$(也就是这些位上 $add$ 都和 $sub$ 相等)都会变成 $1$,这段后的 $1$ 会变成 $0$。

    所以对于一位 $k$,从这一位的低一位开始,找到第一位 $add e sub$ 的位置,如果这一位上 $add>sub$(不借位),那么第 $k$ 位不会变,否则第 $k$ 位会变。

    问题变为比较两个超多位数的数的大小。

    可以维护所有 $add e sub$ 的位(用 set 存),然后求出比 $k$ 低的第一位不同的位,比较即可。

    修改时可以简单修改一下这个 set。

    时间复杂度 $O(nlog n)$。

    下面的代码压了 $30$ 位。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=1111111;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    inline int read(){
        int x=0,f=0;char ch=getchar();
        while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
        while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
        return f?-x:x;
    }
    int n;
    ll add[maxn],sub[maxn];
    set<int,greater<int> > s;
    int main(){
        n=read();read();read();read();
        while(n--){
            int op=read(),x=read(),y;
            if(op==1){
                y=read();
                int a=y/30,b=y%30,cnt=0;
                if(x>0){
                    add[a]+=(ll)x<<b;
                    while(add[a]>>30 || cnt<=5){
                        if(!(add[a]>>30)) cnt++;
                        add[a+1]+=add[a]>>30;
                        add[a]&=(1<<30)-1;
                        if(add[a]!=sub[a]) s.insert(a);
                        else if(s.count(a)) s.erase(a);
                        a++;
                    }
                }
                else if(x<0){
                    sub[a]+=(ll)(-x)<<b;
                    while(sub[a]>>30 || cnt<=5){
                        if(!(sub[a]>>30)) cnt++;
                        sub[a+1]+=sub[a]>>30;
                        sub[a]&=(1<<30)-1;
                        if(add[a]!=sub[a]) s.insert(a);
                        else if(s.count(a)) s.erase(a);
                        a++;
                    }
                }
            }
            else{
                int a=x/30,b=x%30,t1=add[a]&((1<<b)-1),t2=sub[a]&((1<<b)-1),t=((add[a]^sub[a])>>b)&1;
                set<int,greater<int> >::iterator it=s.lower_bound(a-1);
                if(t1>t2 || t1==t2 && (it==s.end() || add[*it]>sub[*it])) printf("%d
    ",t);
                else printf("%d
    ",t^1);
            }
        }
    }
    View Code

    NOI2017D2T1

    题目大意:有一个长度为 $n$ 的字符串 $S$,只包含 $ ext{a,b,c,x}$。现在你要把每个字符都替换成 $ ext{A,B,C}$ 中的一个,其中 $ ext{a}$ 不能替换成 $ ext{A}$,$ ext{b}$ 不能替换成 $ ext{B}$,$ ext{c}$ 不能替换成 $ ext{C}$,$ ext{x}$ 任意。另外有 $m$ 个限制 $p_1,c_1,p_2,c_2$,表示如果第 $p_1$ 个字母被替换成了 $c_1$,那么第 $p_2$ 个字母就一定要被替换成 $c_2$。请求出一种合法方案。如果无解输出 $-1$。

    $1le nle 50000,0le mle 100000$,$ ext{x}$ 的个数(称为 $d$) $le 8$。

    先考虑没有 $d=0$ 怎么做。发现是 2-SAT 裸题。

    然后可以枚举把每个 $ ext{x}$ 看成是 $ ext{a}$ 还是 $ ext{b}$ 还是 $ ext{c}$。所有的合法情况一定都被枚举到了。

    时间复杂度 $O(3^d(n+m))$,不能通过。

    发现只需要枚举 $ ext{a}$ 和 $ ext{b}$ 就够了,也已经把选 $ ext{A,B,C}$ 的情况都考虑到了。

    时间复杂度 $O(2^d(n+m))$。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=50050;
    #define MP make_pair
    #define PB push_back
    #define lson o<<1,l,mid
    #define rson o<<1|1,mid+1,r
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    inline int read(){
        char ch=getchar();int x=0,f=0;
        while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
        while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
        return f?-x:x;
    }
    int n,d,m,c,ai[maxn*2],bi[maxn*2],id[maxn];
    int stk[maxn*2],tp,scnt,scc[maxn*2],dfn[maxn*2],low[maxn*2],dcnt;
    int el,head[maxn*2],to[maxn*4],nxt[maxn*4];
    char s[maxn],hai[maxn*2],hbi[maxn*2];
    bool nota[10],vis[maxn*2];
    inline void add(int u,int v){
        to[++el]=v;nxt[el]=head[u];head[u]=el;
    }
    void tarjan(int u){
        dfn[u]=low[u]=++dcnt;
        vis[stk[++tp]=u]=true;
        for(int i=head[u];i;i=nxt[i]){
            int v=to[i];
            if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
            else if(vis[v]) low[u]=min(low[u],dfn[v]);
        }
        if(dfn[u]==low[u]){
            scnt++;
            do{
                scc[stk[tp]]=scnt;
                vis[stk[tp]]=false;
            }while(stk[tp--]!=u);
        }
    }
    inline int type(char a,char b){
        return a=='a' && b=='c' || a=='b' && b=='c' || a=='c' && b=='b';
    }
    void SAT_2(){
        tp=scnt=dcnt=el=0;MEM(stk,0);MEM(scc,0);MEM(dfn,0);MEM(low,0);MEM(head,0);MEM(to,0);MEM(nxt,0);
        FOR(i,1,n) if(id[i]) s[i]=nota[id[i]]?'a':'b';
        FOR(i,1,m){
            if(s[ai[i]]==hai[i]) continue;
            if(s[bi[i]]==hbi[i]) add(2*ai[i]+type(s[ai[i]],hai[i]),2*ai[i]+!type(s[ai[i]],hai[i]));
            else add(2*ai[i]+type(s[ai[i]],hai[i]),2*bi[i]+type(s[bi[i]],hbi[i])),add(2*bi[i]+!type(s[bi[i]],hbi[i]),2*ai[i]+!type(s[ai[i]],hai[i]));
        }
        FOR(i,2,2*n+1) if(!dfn[i]) tarjan(i);
        FOR(i,1,n) if(scc[2*i]==scc[2*i+1]) return;
        FOR(i,1,n){
            if(s[i]=='a') putchar(scc[2*i]<scc[2*i+1]?'B':'C');
            if(s[i]=='b') putchar(scc[2*i]<scc[2*i+1]?'A':'C');
            if(s[i]=='c') putchar(scc[2*i]<scc[2*i+1]?'A':'B');
        }
        exit(0);
    }
    void dfs(int dep){
        if(dep>d) return SAT_2();
        dfs(dep+1);
        nota[dep]=true;
        dfs(dep+1);
        nota[dep]=false;
    }
    int main(){
        n=read();d=read();
        scanf("%s",s+1);
        FOR(i,1,n) if(s[i]=='x') id[i]=++c;
        m=read();
        FOR(i,1,m){
            ai[i]=read();
            while(hai[i]<'A' || hai[i]>'C') hai[i]=getchar();hai[i]+='a'-'A';
            bi[i]=read();
            while(hbi[i]<'A' || hbi[i]>'C') hbi[i]=getchar();hbi[i]+='a'-'A';
        }
        dfs(1);
        printf("-1");
    }
    View Code

    NOI2018D1T1

    题目大意:$T$ 组数据。给一个 $n$ 个点 $m$ 条边的无向连通图,每条边有长度 $l_i$ 和权值 $a_i$。$q$ 次询问,每次给定起点 $u$ 和权值下限 $x$,问对于所有能只经过 $a_i>x$ 的边就走到 $u$ 的点,到 $1$ 的最短路的最小值。强制在线。

    $1le Tle 3,1le nle 2 imes 10^5,0le m,qle 4 imes 10^5$。

    首先预处理每个点到 $1$ 的最短路。

    将边按 $a_i$ 从大到小排序,然后建出 kruskal 重构树,那么 $u$ (新点,代表原来的边)的子树中就是可以只经过超过 $a_u$ 的边互达的点。每次询问时,从点 $u$(原点)倍增,跳到第一个父亲的 $ale x$ 的位置,答案就是这棵子树中最短路的最小值。

    时间复杂度 $O(T(mlog n+mlog m+qlog n))$。

    #include<bits/stdc++.h>
    using namespace std;
    #define mem(x) (memset(x,0,sizeof(x)))
    typedef long long ll;
    const int maxn=200020,maxm=400040;
    struct edge1{
        int u,v,w;
        bool operator<(const edge1 e)const{
            return w>e.w;
        }
    }e1[maxm];
    struct edge2{
        int v,w,nxt;
    }e2[maxm<<1],e3[maxn<<1];
    struct state{
        int u;ll dis;
        bool operator<(const state s)const{
            return dis>s.dis;
        }
    };
    int t,n,m,q,k,s,lastans;
    int el2,el3,head2[maxn],head3[maxn<<1];
    int u_fa[maxn<<1],cnt;
    ll dis[maxn],w[maxn<<1],mind[maxn<<1],fa[maxn<<1][21];
    priority_queue<state> pq;
    inline void add2(int u,int v,int w){
        e2[++el2]=(edge2){v,w,head2[u]};head2[u]=el2;
    }
    inline void add3(int u,int v){
        e3[++el3]=(edge2){v,0,head3[u]};head3[u]=el3;
    }
    void dijkstra(){
        while(!pq.empty()) pq.pop();
        memset(dis,0x3f,sizeof(dis));
        dis[1]=0;
        pq.push((state){1,0});
        while(!pq.empty()){
            int u=pq.top().u;ll d=pq.top().dis;pq.pop();
            if(d>dis[u]) continue;
            for(int i=head2[u];i;i=e2[i].nxt){
                int v=e2[i].v;
                if(dis[u]+e2[i].w<dis[v]) pq.push((state){v,dis[v]=dis[u]+e2[i].w});
            }
        }
    }
    int getfa(int x){
        return x==u_fa[x]?x:u_fa[x]=getfa(u_fa[x]);
    }
    void kruskal(){
        sort(e1+1,e1+m+1);
        for(int i=1;i<=n*2-1;i++) u_fa[i]=i;
        for(int i=1;i<=n;i++) w[i]=-1e18;
        cnt=n;
        for(int i=1;i<=m;i++){
            int u=e1[i].u,v=e1[i].v;
            u=getfa(u);v=getfa(v);
            if(u==v) continue;
            w[++cnt]=e1[i].w;
            u_fa[u]=u_fa[v]=cnt;
            add3(cnt,u);add3(cnt,v);
        }
    }
    void dfs(int u){
        mind[u]=u>n?1e18:dis[u];
        for(int i=1;i<=20;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
        for(int i=head3[u];i;i=e3[i].nxt){
            int v=e3[i].v;
            fa[v][0]=u;
            dfs(v);
            mind[u]=min(mind[u],mind[v]);
        }
    }
    ll calc(ll st,ll p){
        for(int i=20;~i;i--)
            if(w[fa[st][i]]>p) st=fa[st][i];
        return mind[st];
    }
    int main(){
        scanf("%d",&t);
        while(t--){
            mem(e1);mem(e2);mem(e3);mem(head2);mem(head3);mem(w);mem(mind);mem(fa);
            w[lastans=el2=el3=cnt=0]=-1e18;
            scanf("%d%d",&n,&m);
            for(int i=1;i<=m;i++){
                int u,v,w1,w2;
                scanf("%d%d%d%d",&u,&v,&w1,&w2);
                e1[i]=(edge1){u,v,w2};
                add2(u,v,w1);add2(v,u,w1);
            }
            dijkstra();
            kruskal();
            fa[cnt][0]=0;
            dfs(cnt);
            scanf("%d%d%d",&q,&k,&s);
            for(int i=1;i<=q;i++){
                int st,p;
                scanf("%d%d",&st,&p);
                st=(st+k*lastans-1)%n+1;
                p=(p+k*lastans)%(s+1);
                printf("%lld
    ",lastans=calc(st,p));
            }
        }
    }
    View Code

    NOI2018D2T1

    之前写过。

  • 相关阅读:
    补发《超级迷宫》站立会议九
    补发《超级迷宫》站立会议八
    补发《超级迷宫》站立会议七
    补发《超级迷宫》站立会议六
    一周开发项目
    所学的内容
    开发项目和所用时间 感想
    自我介绍
    大容量数据转移操作命令——BULK INSERT(类似于BCP)
    字符编码与文件处理
  • 原文地址:https://www.cnblogs.com/1000Suns/p/10999409.html
Copyright © 2011-2022 走看看