NOI Online 提高


序列

題意

有長度為\(n\)\(a,b\)兩個序列,有m種操作,操作有兩種類型,\(1\ x\ y\)代表這個操作可以讓\(a[x]\)\(a[y]\)同時加\(1\)或者減\(1\),\(2\ x\ y\)則代表一個加\(1\)另一個減\(1\)
每種操作可以無限次進行,問能否讓\(a\)\(b\)完全相等。

題解

先讓 \(a[i]=a[i]-b[i]\) ,現在的目的就是讓 \(a[i]=0\)
首先分兩種部分分情況:

    1.只有兩個點
    2.操作只有2

只有兩個點的情況很容易處理,討論一下就可以了。
操作只有2的情況也很容易處理,把操作看成邊,發現2邊連接的連通塊之和為0的話,這個連通塊就可以通過不斷的2操作和b完全一樣。

整體做法就是結合上面兩種情況,我們先把2邊連通塊縮成一個點,這時候整個圖就剩1邊了,發現性質,a-b-c這樣一個結構,可以看做是a和c連了個2邊。

讓新的圖建立起新的2邊,然后再縮點,這個過程可以通過黑白染色實現。這樣縮下去,每個連通塊的點數不超過\(2\),依次判斷就行。

復雜度是\(O(n)\)

代碼

考場沒寫出來,只寫了暴力。

現在發現正解比暴力代碼好寫:

#include <bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,m;
vector<int> q2[MAXN],q1[MAXN],q[MAXN];
int tot;
int idx[MAXN],vis[MAXN];
long long a[MAXN],B[MAXN],v[MAXN];

void dfs_init(int x)
{
    //cout<<" dfs_init "<<x<<" "<<tot<<endl;
    idx[x]=tot;
    v[tot]+=a[x];
    for(int i=0; i<q2[x].size(); i++)
    {
        int nx=q2[x][i];
        if(idx[nx])
            continue;
        dfs_init(nx);
    }
}
bool ok=0;
stack<int> b,w;
void dfs(int x)
{
    if(vis[x]==1)
        b.push(x);
    else
        w.push(x);
    for(int i=0; i<q[x].size(); i++)
    {
        int nx=q[x][i];
        if(vis[nx]==vis[x])
            ok=1;
        if(vis[nx])
            continue;
        vis[nx]=3-vis[x];
        dfs(nx);
    }
}

int main()
{
    int T;scanf("%d",&T);

    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++)
            scanf("%lld",&a[i]);
        for(int i=1; i<=n; i++)
            scanf("%lld",&B[i]),a[i]-=B[i];
        for(int i=1; i<=m; i++)
        {
            int t,x,y;
            scanf("%d%d%d",&t,&x,&y);
            if(t==1)
            {
                q1[x].push_back(y);
                q1[y].push_back(x);
            }
            else
            {
                q2[x].push_back(y);
                q2[y].push_back(x);
            }
        }
        for(int i=1; i<=n; i++)
        {
            if(idx[i])
                continue;
            tot++;
            dfs_init(i);
        }
        for(int i=1; i<=n; i++)
        {
            for(int j=0; j<q1[i].size(); j++)
            {
                int nx=q1[i][j];
                q[idx[i]].push_back(idx[nx]);
                q[idx[nx]].push_back(idx[i]);
            }
        }
        for(int i=1;i<=tot;i++){
            for(int j=0;j<q[i].size();j++){
                int nx=q[i][j];
            }
        }
        bool ans=1;
        for(int i=1; i<=tot; i++)
        {
            ok=0;
            if(vis[i])
                continue;
            long long val=0;
            vis[i]=1;
            dfs(i);
            if(ok){
                while(!b.empty()){
                    val+=v[b.top()];
                    b.pop();
                }
                while(!w.empty()){
                    val+=v[w.top()];
                    w.pop();
                }
                if(val%2)
                    ans=0;
            }
            else{
                if(w.empty()){
                    if(v[b.top()]!=0)
                        ans=0;
                    b.pop();
                }
                else{
                    while(!b.empty()){
                        val+=v[b.top()];
                        b.pop();
                    }
                    while(!w.empty()){
                        val-=v[w.top()];
                        w.pop();
                    }
                    if(val!=0)
                        ans=0;
                }
            }
        }
        if(ans)
            printf("YES\n");
        else
            printf("NO\n");
        for(int i=1;i<=tot;i++)
        {
            q[i].clear();
            v[tot]=0;
        }
        for(int i=1;i<=n;i++)
        {
            vis[i]=idx[i]=v[i]=0;
            q1[i].clear();
            q2[i].clear();
        }
        tot=0;
        ok=0;

    }

    return 0;
}

冒泡排序

題意

\(n\)長度序列\(p\)\(m\)個操作,操作分兩種,\(1\ x\)表示當前序列\(x\)位和\(x+1\)位交換,\(2\ k\)表示詢問如果當前序列冒泡\(k\)輪后的序列的逆序對和。

題解

我們逆序對計算方式是算前面比當前數大的個數。那么會發現,一個序列冒泡一次,每個位置的逆序對數量都會\(-1\),除非那個位置逆序對數為0。

那么我們用樹狀數組(線段樹也行)去存儲逆序對數,樹狀數組第k個位置存的就是逆序對數為k的位置的逆序對數。查詢時查詢大於\(k\)的和,然后每個大於\(k\)的位置都減去\(k\)
即。

代碼

#include <bits/stdc++.h>
using namespace std;
const int MAXN=200005;
int n,m;
int p[MAXN];
long long sum[MAXN],maxn[MAXN],tot[MAXN];
int k[MAXN];
void update(int l,long long v,long long *sum){
    while(l){
        sum[l]+=v;
        l-=(l&(-l));
    }
}
long long get(int l,long long *sum){
    long long an=0;
    while(l<=n){
        an+=sum[l];
        l+=(l&(-l));
    }
    return an;
}

int main()
{
	freopen("bubble.in","r",stdin);
	freopen("bubble.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&p[i]);
        update(p[i],1,maxn);
        k[i]=get(p[i]+1,maxn);
        update(k[i],k[i],sum);
        update(k[i],1,tot);
    }

    for(int i=1;i<=m;i++){
        int t,x;
        scanf("%d%d",&t,&x);
        if(t==1){
            if(p[x]>p[x+1])
            {
                update(k[x+1],-k[x+1],sum);
                update(k[x+1],-1,tot);
                k[x+1]--;
                update(k[x+1],k[x+1],sum);
                update(k[x+1],1,tot);
            }
            else{
                update(k[x],-k[x],sum);
                update(k[x],-1,tot);
                k[x]++;
                update(k[x],k[x],sum);
                update(k[x],1,tot);
            }
            swap(p[x],p[x+1]);
            swap(k[x],k[x+1]);
        }
        else{
            long long ans=get(x+1,sum)-x*get(x+1,tot);
            printf("%lld\n",ans);
        }
    }

    return 0;
}

最小環

題意

給你個\(n\)的環,環上每個位置都有值,給\(m\)次詢問,對於每次詢問給個\(k\),你需要把環上的值交換順序,以讓環上每個相距為\(k\)的數相乘的和最大。

題解

對於詢問\(k\),相當於是把環分成\(gcd(n,k)\)個小環,發現每個小環都是從大到小分配數的規律,然后因為環的種類只有\(\sum gcd(i,n)\)種,預處理即可。復雜度\(o(n\sqrt{n})\)

代碼

#include <bits/stdc++.h>
using namespace std;

int n,m;
long long a[400005];
bool vis[400005];
long long ans[400005];
int main()
{
	freopen("ring.in","r",stdin);
	freopen("ring.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]),ans[0]+=a[i]*a[i];
    sort(a+1,a+1+n);

    for(int i=1;i*2<=n;i++){
        int g=__gcd(i,n);
        if(vis[g])continue;
        vis[g]=1;
        int l=n/g;
        int p=n-l+1;
        ans[g]+=a[n]*a[n-1];
        for(int j=n;j>=1;j--){
            if(j==p){
                if(j>1)ans[g]+=a[j-1]*a[j-2];
                p=j-l;continue;
            }
            ans[g]=ans[g]+(a[j]*a[max(j-2,p)]);
        }
    }

    for(int i=1;i<=m;i++){
        int k;scanf("%d",&k);
        if(k==0)printf("%lld\n",ans[0]);
        else{
        	k=__gcd(k,n);
        	printf("%lld\n",ans[k]);
    	}
    }
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM