[72] (多校联训) A层冲刺NOIP2024模拟赛25

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';
        }
    }
}

这是什么

放点福瑞

posted @ 2024-11-22 15:52  HaneDaniko  阅读(29)  评论(0编辑  收藏  举报