[72] (多校聯訓) A層衝刺NOIP2024模擬賽25

HaneDaniko發表於2024-11-22
flowchart TB A(圖) style A color:#ffffff,fill:#00c0c0,stroke:#ffffff

用了 bitset,複雜度比較懸以及完全沒用到題目裡提示的 __builtin_popcountll(),所以有點心裡沒底

最重要的是最後三分鐘拍子掛了,給我嚇一跳,後來檢查發現是 Linux 莫名其妙的問題,答案檔案沒更新

首先原問題可以轉化到一個 \(n\times n\) 的二維二進位制序列上,序列中 \((i,j)\) 表示 \(i,j\) 之間有邊(其實這個序列有一半完全沒用,但是也只能讓它沒用了),原問題可以轉化成在這個序列中異或

對給定的序列 \(S,T\),考慮先欽定一個點 \(i\)(對應序列第 \(i\) 行),然後計算哪些點與 \(i\) 的連邊會改變(即將這些值在序列上賦成 \(1\),然後與二進位制序列第 \(i\) 行異或)

由於只能連 \(i\lt j\) 的,欽定 \(i\) 為數對中的較小值,那麼維護出一個形如 \(000\cdots111\)(長度為 \(n\),有 \(i\)\(0\))的序列 \(R\),最終會被更新的點的集合即為 \((S\operatorname{and}R)\operatorname{or}(T\operatorname{and}R)\),直接暴力套 bitset 即可

#include<bits/stdc++.h>
using namespace std;
int n,m;
bitset<10001>ans[10001];
string S;
bitset<10001>s,t,r;
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;++i){
        cin>>S;S=" "+S;
        for(int i=1;i<=n;++i){
            s[i]=((S[i]-'0')>>1)&1;
            t[i]=(S[i]-'0')&1;
            r[i]=1;
        }
        for(int i=1;i<=n;++i){
            r[i]=0;
            if(s[i] and t[i]){
                ans[i]^=((t&r)|(s&r));
            }
            else if(t[i]){
                ans[i]^=(s&r);
            }
            else if(s[i]){
                ans[i]^=(t&r);
            }
        }
    }
    int anss=0;
    for(int i=1;i<=n;++i){
        ans[i][i]=0;
        anss+=ans[i].count();
    }
    cout<<anss<<'\n';
}
flowchart TB A(序列) style A color:#ffffff,fill:#00c0c0,stroke:#ffffff

考慮把所有數都轉成乘法

  • 對於乘法,顯然不用變
  • 對於加法 \(a_i+y\),貪心地想,當我們選到這個加法操作時,其餘形如 \(a_i+y'\)\(y'\) 更大的操作肯定已經被選了,因此這個操作會將值改變成 \(\frac{a_i+y+\sum y'}{a_i+\sum y'}\),其中 \(\sum y'\) 是所有值大於 \(y\) 的值的和(這裡要注意相等的情況,欽定一個嚴格的大小關係就好了)
  • 對於 \(a_i=y\),由於可能會有很多賦值操作,為了儘量減少損失,肯定是將賦值操作放最前面(避免覆蓋掉加和乘的貢獻),因此,當放了一個最大的覆蓋操作的時候,再放較小的覆蓋操作就沒有影響了,比如現在有兩個覆蓋操作 \(2,3\),可以先用 \(2\) 再用 \(3\),只要最大的在最後,結果就不變,因此不是最大值的覆蓋操作可以直接忽略,對於那些是最大值的覆蓋操作,由於可以看做是初始值改變,即 \(\frac{y-a_i}{a_i}\)

這樣就可以直接按值排序了

寫出來以後首先是被卡了 __int128
其次被卡了對 \(p\) 處理不當的問題,就是說如果現在有一個操作,其值為 \(\frac{p}{p-1}\),顯然它在模意義下會變成 \(\frac{0}{p-1}\),答案顯然會變成 \(0\),但這個時候如果後面來了一個 \(\frac{p+1}{p}\),和前面那個乘起來是 \(\frac{p+1}{p-1}\),模意義下是 \(\frac{1}{p-1}\),這會讓答案不再是 \(0\),但是由於上個操作已經將答案賦值成 \(0\) 了,因此這一次答案會錯誤地繼續保留 \(0\),這顯然是不對的
我的寫法是,當遇到形如 \(\frac{p}{a}\) 的數的時候,停止更新 \(ans\),並且記錄當前值(然後直接輸出 \(0\)),然後後面來的 \(\frac{b}{p}\) 去嘗試將前面記錄的值消成 \(\frac{b}{a}\),並用這個值去更新保留的那個 \(ans\),直到記錄的值被消完,恢復對 \(ans\) 的統計
然後又被精度卡了,需要寫分數類

#include<bits/stdc++.h>
using namespace std;
#define int __int128
const int p=1e9+7;
int power(int a,int t){
    int base=a,ans=1;
    while(t){
        if(t&1){
            ans=ans*base%p;
        }
        base=base*base%p;
        t>>=1;
    }
    return ans;
}
int n,m;
int a[100001];
struct ope_t{
    int z,m,id;
    bool operator<(const ope_t&A)const{
        return z*A.m>A.z*m;
    }
};
vector<ope_t>v;
vector<int>add[100001];
int maxn[100001];
ope_t st[100001];
signed main(){
    cin>>n>>m;
    memset(maxn,-1,sizeof maxn);
    for(int i=1;i<=n;++i){
        cin>>a[i];
    }
    for(int i=1;i<=m;++i){
        int opt,x,y;
        cin>>opt>>x>>y;
        if(opt==1){
            maxn[x]=max(maxn[x],y);
        }
        else if(opt==2){
            add[x].push_back(y);
        }
        else{
            v.push_back({y,1,x});
        }
    }
    for(int i=1;i<=n;++i){
        if(maxn[i]>a[i]) add[i].push_back(maxn[i]-a[i]);
        sort(add[i].begin(),add[i].end(),greater<int>());
        int sum=a[i];
        for(int j:add[i]){
            v.push_back({sum+j,sum,i});
            sum+=j;
        }
    }
    sort(v.begin(),v.end());
    int ans=1;
    for(int i=1;i<=n;++i){
        ans=ans*a[i]%p;
    }
    cout<<ans;putchar(' ');
    int unfcnt=0;
    for(ope_t i:v){
        if(ans*i.z%p*power(i.m,p-2)%p!=0){
            ans=ans*i.z%p*power(i.m,p-2)%p;
            cout<<ans;
        }
        else if(i.z%p==0){
            st[i.id]={i.z,i.m,i.id};
            cout<<0;
            unfcnt++;
        }
        else if(i.m%p==0){
            if(st[i.id].id){
                ans=ans*i.z%p*power(st[i.id].m,p-2)%p;
                st[i.id]={};
            }
            unfcnt--;
            if(!unfcnt) cout<<ans;
            else cout<<'0';
        }
        putchar(' ');
    }
    for(int i=1;i<=m-(int)v.size();++i){
        cout<<ans;putchar(' ');
    }
    cout<<endl;
}
flowchart TB A(字串) style A color:#ffffff,fill:#00c0c0,stroke:#ffffff

先考慮怎麼處理詢問

用給定排列 \(t\) 內的序號給每個字元重新編號,可以發現原問題等價於求當前序列劃分成若干上升子序列的最小個數,而這個個數等價於求序列逆序對個數,因此我們需要維護這個逆序對個數

由於 \(k\) 只有 \(10\),可以考慮直接列舉值域,維護 \((i,j)\) 在原序列中出現的次數,然後查詢的時候直接算哪些值在重新編號後是逆序對,暴力統計這些數對的數量即為答案

因此我們需要動態地維護每隊數在原序列中出現的次數,注意到這個東西是有結合律的,而且要求支援區間修改,因此直接上線段樹,線段樹節點裡暴力維護 \(k^2\) 的數對資訊,修改操作實質是模意義下的區間加,正常做就行了

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
string S;
namespace stree{
    struct tree{
        int cnt[10][10]; //記錄區間 [l,r+1] 內相鄰的值為 [i,j] 的對數
        int lazy;
        int lc,rc;
        tree operator+(const tree&A)const{
            tree ans;
            for(int i=0;i<=k-1;++i){
                for(int j=0;j<=k-1;++j){
                    ans.cnt[i][j]=cnt[i][j]+A.cnt[i][j];
                }
            }
            ans.lc=lc;
            ans.rc=A.rc;
            ans.cnt[rc][A.lc]++;
            return ans;
        }
    }t[200001*4];
    #define tol (id*2)
    #define tor (id*2+1)
    #define mid(l,r) mid=((l)+(r))/2
    void update(int id){
        // cout<<"update "<<id<<endl;
        for(int i=0;i<=k-1;++i){
            for(int j=0;j<=k-1;++j){
                t[id].cnt[i][j]=t[tol].cnt[i][j]+t[tor].cnt[i][j];
            }
        }
        t[id].lc=t[tol].lc;
        t[id].rc=t[tor].rc;
        // cout<<t[tol].rc<<' '<<t[tor].lc<<endl;
        // assert(t[tol].rc>=0 and t[tol].rc<=k and t[tor].lc>=0 and t[tor].lc<=k);
        t[id].cnt[t[tol].rc][t[tor].lc]++;
    }
    void build(int id,int l,int r){
        // cout<<"build "<<id<<" "<<l<<" "<<r<<endl;
        // assert(id!=0);
        if(l==r){
            t[id].lc=t[id].rc=S[l]-'a';
            return;
        }
        int mid(l,r);
        build(tol,l,mid);
        build(tor,mid+1,r);
        update(id);
    }
    void actlazy(int id,int lazy){
        tree ans;
        for(int i=0;i<=k-1;++i){
            for(int j=0;j<=k-1;++j){
                // cout<<" "<<i<<" "<<lazy<<endl;
                // if(i+lazy<0) cout<<i<<" "<<lazy<<" when actlazy "<<id<<endl;
                // assert((i+lazy)>=0);
                ans.cnt[(i+lazy)%k][(j+lazy)%k]=t[id].cnt[i][j];
            }
        }
        ans.lc=t[id].lc;
        ans.rc=t[id].rc;
        ans.lazy=t[id].lazy;
        t[id]=ans;
    }
    void pushdown(int id){
        if(t[id].lazy){
            t[tol].lazy+=t[id].lazy;
            t[tor].lazy+=t[id].lazy;
            t[tol].lc=(t[tol].lc+t[id].lazy)%k;
            t[tor].lc=(t[tor].lc+t[id].lazy)%k;
            t[tol].rc=(t[tol].rc+t[id].lazy)%k;
            t[tor].rc=(t[tor].rc+t[id].lazy)%k;
            // if(tol==18) cout<<"actlazyl "<<tol<<" "<<t[id].lazy<<" of "<<id<<endl;
            actlazy(tol,t[id].lazy);
            // if(tol==18) cout<<"actlazyr "<<tor<<" "<<t[id].lazy<<endl;
            actlazy(tor,t[id].lazy);
            t[id].lazy=0;
        }
    }
    void change(int id,int l,int r,int L,int R,int val){
        // cout<<"change "<<id<<" "<<l<<" "<<r<<" "<<L<<" "<<R<<" "<<val<<endl;
        if(L<=l and r<=R){
            // if(id==9) cout<<t[id].lazy<<"+="<<val<<endl;
            t[id].lazy+=val;
            t[id].lc=(t[id].lc+val)%k;
            t[id].rc=(t[id].rc+val)%k;
            // if(id==18) cout<<"actlazy "<<id<<" "<<val<<endl;
            actlazy(id,val);
            return;
        }
        int mid(l,r);
        pushdown(id);
        if(R<=mid) change(tol,l,mid,L,R,val);
        else if(L>=mid+1) change(tor,mid+1,r,L,R,val);
        else{
            change(tol,l,mid,L,mid,val);
            change(tor,mid+1,r,mid+1,R,val);
        }
        update(id);
    }
    tree ask(int id,int l,int r,int L,int R){
        if(L<=l and r<=R){
            return t[id];
        }
        pushdown(id);
        int mid(l,r);
        if(R<=mid) return ask(tol,l,mid,L,R);
        else if(L>=mid+1) return ask(tor,mid+1,r,L,R);
        return ask(tol,l,mid,L,mid)+ask(tor,mid+1,r,mid+1,R);
    }
}
signed main(){
    freopen("d.in","r",stdin);
    freopen("d.out","w",stdout);
    // freopen("/home/hdk/code/11.21/down/d/d2.in","r",stdin);
    // freopen("out.out","w",stdout);
    ios::sync_with_stdio(false);
    cin>>n>>m>>k;
    // cout<<":::"<<n<<" "<<m<<" "<<k<<endl;
    cin>>S;S=" "+S;
    stree::build(1,1,n);
    while(m--){
        int opt,l,r;cin>>opt>>l>>r;
        if(opt==1){
            int c;cin>>c;
            stree::change(1,1,n,l,r,c);
        }
        else{
            string t;cin>>t;
            stree::tree tmp=stree::ask(1,1,n,l,r);
            int ans=0;
            for(int i=0;i<=(int)t.length()-1;++i){
                for(int j=0;j<=i;++j){
                    ans+=tmp.cnt[t[i]-'a'][t[j]-'a'];
                }
            }
            cout<<ans+1<<'\n';
        }
    }
}

這是什麼

放點福瑞

相關文章