可达 2025 暑期集训笔记:CSP-S 沃斯班

Day 1

字典树

const int _mxn=1e6+5;
struct trie
{
    int tr[_mxn][26],idx,fl[_mxn],cnt[_mxn];
    void insert(string s)
    {
        int nw=0;
        for(int i=0;i<s.size();i++)
        {
            int t=s[i]-'a';
            if(!tr[nw][t])
                tr[nw][t]=++idx;
            nw=tr[nw][t];
            cnt[nw]++;
        }
        fl[nw]++;
    }
    int query(string s)
    {
        int nw=0;
        for(int i=0;i<s.size();i++)
        {
            int t=s[i]-'a';
            if(!tr[nw][t])
                return -1;
            nw=tr[nw][t];
        }
        return fl[nw];
    }
}tr;

Day 1 模拟赛

A

题意

现有 \(n\) 个人前来排队买票,其中第 \(0\) 人站在队伍最前方 ,第 \((n - 1)\) 人站在队伍最后方。给定每个人要买的票数,每个人买一张票都需要用掉 恰好 \(1\) 秒,之后到队尾重新排队,如果买完就离队。求第 \(k\) 人完成买票需要的时间/秒。

题解

直接用队列模拟,每个元素用 pair 存编号和剩余要买票数。

代码:

#define ll long long
const int _mxn=1000+5;
int n,k,a[_mxn];
int main()
{
    ___();
    cin>>n>>k;
    queue<pair<int,int> > q;
    for(int i=0;i<n;i++)//注意是 0~(n-1)
        cin>>a[i],q.push(make_pair(i,a[i]));
    for(int i=1;;i++)
    {
        int id=q.front().first,t=q.front().second-1;
        q.pop();
        if(t==0)
        {
            if(id==k)//k 买完了
            {
                cout<<i<<endl;//输出时间
                break;//结束
            }
        }
        else//还有要买的,重新入队
            q.push(make_pair(id,t));
    }
    return 0;
}

B

题意

给定一个数组,求出其中第 \(k\) 大的数字减去第 \(k\) 小的数字的值 \(m\),并判断 \(m\) 是否为质数。

题解

大水题。直接排个序把 \(m\) 求出来,然后标准试除法判质数即可。

代码:

#define ll long long
const int _mxn=10000+5;
int n,k,a[_mxn];
bool ispri(int x)
{
    if(x<2)
        return false;
    for(int i=2;i*i<=x;i++)
        if(x%i==0)
            return false;
    return true;
}
int main()
{
    ___();
    cin>>n>>k;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    sort(a+1,a+n+1);
    int ans=a[n-k+1]-a[k];
    cout<<(ispri(ans)?"YES":"NO")<<endl;
    cout<<ans<<endl;
    return 0;
}

C

题意

给定一个数组 \(b\),请你找到一对数,满足 \(b_i\oplus b_j\ge k\) 且使得 \(j−i+1\) 的值尽可能小,输出最小值。如果找不到这样一对数,则输出 \(-1\)

题解

考虑用 01 Trie 维护异或值。在插入时处理子树对应 \(i\) 值的最大值,查询时同时把当前数和 \(k\) 的对应二进制位取出来,分情况讨论。具体看代码:

#define ll long long
const int _mxn=2e5+5;
int tr[_mxn*32][2],cnt=0,mxi[_mxn*32];
int n,k,b[_mxn];
void insert(int x,int id)
{
    int nw=0;
    for(int i=30;i>=0;i--)
    {
        mxi[nw]=max(mxi[nw],id);//求出最大 i
        int t=(x>>i)&1;
        if(!tr[nw][t])
            tr[nw][t]=++cnt;
        nw=tr[nw][t];
    }
    mxi[nw]=max(mxi[nw],id);
}
int query(int x,int id)
{
    int nw=0,res=0;
    for(int i=30;i>=0;i--)
    {
        int tx=(x>>i)&1,tk=(k>>i)&1;
        if(tk)//k 这一位为 1
        {
            if(tr[nw][!tx])//异或不一样为 1,所以找不同的
                nw=tr[nw][!tx];
            else//没有就无解,返回一个极大值
                return _mxn;
        }
        else//为 0
        {
            if(tr[nw][!tx])//有不同的,后面怎么取都比 k 大,直接求答案返回
                return id-mxi[tr[nw][!tx]]+1;
            else//直接往下遍历
                nw=tr[nw][tx];
        }
    }
    return id-mxi[nw]+1;
}
int main()
{
    ___();
    cin>>n>>k;
    if(k==0)//特判,保险
    {
        cout<<1<<endl;
        return 0;
    }
    int ans=_mxn;
    for(int i=1;i<=n;i++)
    {
        cin>>b[i];
        insert(b[i],i);
        ans=min(ans,query(b[i],i));//取最小值
    }
    cout<<(ans>n?-1:ans)<<endl;
    return 0;
}

D

题意

洛谷 P4551

题解

取一个根节点,预处理每个点到根节点的异或和。不难发现,\(\operatorname{xor}(u,v)=\operatorname{xor}(u,\operatorname{root})\oplus\operatorname{xor}(v,\operatorname{root})\),所以问题就被转换成了一个数组取两个数求最大异或值(洛谷 P10471)。用 01 Trie 维护,贪心选不同的,可以最大。

代码:

#define ll long long
const int _mxn=1e5+5;
int tr[_mxn*32][2],cnt=0;
void insert(int x)
{
    int nw=0;
    for(int i=31;i>=0;i--)
    {
        int t=(x>>i)&1;
        if(!tr[nw][t])
            tr[nw][t]=++cnt;
        nw=tr[nw][t];
    }
}
ll query(ll x)
{
    int nw=0;
    ll res=0;
    for(int i=31;i>=0;i--)
    {
        int t=(x>>i)&1;
        if(tr[nw][!t])//找不同的
        {
            res+=1<<i;//加上
            nw=tr[nw][!t];
        }
        else//没有不同的
            nw=tr[nw][t];
    }
    return res;
}
int n,a[_mxn];
typedef int w_type;
struct node
{
    int v;
    w_type w;
    node(){}
    node(int _v,w_type _w):v(_v),w(_w){}
    bool operator<(node x) const {return w<x.w;}
    bool operator>(node x) const {return w>x.w;}
};
vector<node> g[_mxn];
void add(int u,int v,w_type w){g[u].push_back(node(v,w));}
void dfs(int u,int fa)//预处理每个点到根节点的异或和
{
    for(auto it:g[u])
    {
        if(it.v==fa)
            continue;
        a[it.v]=(a[u]^it.w);
        dfs(it.v,u);
    }
}
int main()
{
    ___();
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w),add(v,u,w);
    }
    a[1]=0;//根节点取个 1
    dfs(1,-1);
    for(int i=1;i<=n;i++)//预处理出来的值插入字典树
        insert(a[i]);
    ll ans=0;
    for(int i=1;i<=n;i++)//求最大值
        ans=max(ans,query(a[i]));
    cout<<ans<<endl;
    return 0;
}

E

题意

洛谷 P8306(这个模拟赛样例都没改,一堆 fusu())

题解

字典树板子。

#define ll long long
const int _mxn=3e6+5;
int tr[_mxn][64],cnt=0,fl[_mxn];
int chtoint(char c)//字符转数字
{
    if(isdigit(c))
        return c-'0';
    if(isupper(c))
        return c-'A'+1+'9'-'0';
    else 
        return c-'a'+1+'Z'-'A'+1+'9'-'0';
}
void insert(string s)
{
    int nw=0;
    for(int i=0;i<s.size();i++)
    {
        int t=chtoint(s[i]);
        if(!tr[nw][t])
            tr[nw][t]=++cnt;
        nw=tr[nw][t];
        fl[nw]++;//统计经过这里的字符串个数
    }
}
int query(string s)
{
    int nw=0;
    for(int i=0;i<s.size();i++)
    {
        int t=chtoint(s[i]);
        if(!tr[nw][t])
            return 0;
        nw=tr[nw][t];
    }
    return fl[nw];//输出个数
}
int main()
{
    ___();
    int _;
    cin>>_;
    while(_--)
    {
        for(int i=0;i<=cnt;i++)//多测清零
            for(int j=0;j<62;j++)
                tr[i][j]=0;
        for(int i=0;i<=cnt;i++)
            fl[i]=0;
        cnt=0;
        int n,q;
        cin>>n>>q;
        for(int i=1;i<=n;i++)
        {
            string s;
            cin>>s;
            insert(s);
        }
        while(q--)
        {
            string t;
            cin>>t;
            cout<<query(t)<<endl;
        }
    }
    return 0;
}

F

题意

在魔法研究院中,需要设计一个符文词典系统,支持以下操作:

符文刻录:将新的符文序列记录到词典中
符文占卜:检查是否存在与占卜模式匹配的符文序列(占卜符 . 可匹配任意单个符文)

题解

字典树板子,但是查询要动点手脚。直接看代码吧:(话说这么暴力似乎还是正解)

#define ll long long
const int _mxn=1e4+5;
int tr[_mxn*25][26],cnt;
bool fl[_mxn*25];
void insert(string s)
{
    int nw=0;
    for(int i=0;i<s.size();i++)
    {
        int t=s[i]-'a';
        if(!tr[nw][t])
            tr[nw][t]=++cnt;
        nw=tr[nw][t];
    }
    fl[nw]=true;
}
bool query(string s,int nw,int st)
{
    for(int i=st;i<s.size();i++)
    {
        int t=s[i]-'a';
        if(s[i]=='.')
        {
            for(s[i]='a';s[i]<='z';s[i]++)//是 . 就枚举所有字母改一下然后递归查
                if(tr[nw][s[i]-'a']&&query(s,nw,i))
                    return true;
            return false;
        }
        else if(!tr[nw][t])
            return false;
        nw=tr[nw][t];
    }
    return fl[nw];
}
int main()
{
    ___();
    string op,s;
    while(cin>>op>>s)
    {
        if(op=="add")
            insert(s);
        else
            cout<<(query(s,0,0)?"true":"false")<<endl;
    }
    return 0;
}

Day 2

单调栈、单调队列

Day 2 模拟赛

A

题意

洛谷 P1618

题解

我直接暴力 dfs 全排列。记得化简给的比例,不然就是 95 分 WA 到底错哪了()(话说不化简到洛谷能过)

代码:

#define ll long long
const int _mxn=+5;
int a,b,c;
int t[15],vis[15];
bool f=false;
void dfs(int dep)
{
    if(dep>9)
    {
        int x=0,y=0,z=0;
        for(int i=1;i<=3;i++)
            x=x*10+t[i];
        for(int i=4;i<=6;i++)
            y=y*10+t[i];
        for(int i=7;i<=9;i++)
            z=z*10+t[i];
        if(x%a==0&&y%b==0&&z%c==0&&x/a==y/b&&x/a==z/c)//判断一下
            f=true,cout<<x<<" "<<y<<" "<<z<<endl;
        return;
    }
    for(int i=1;i<=9;i++)
    {
        if(!vis[i])
        {
            vis[i]=true;
            t[dep]=i;
            dfs(dep+1);
            vis[i]=false;
        }
    }
}
int main()
{
    ___();
    cin>>a>>b>>c;
    int g=__gcd(__gcd(a,b),c);
    a/=g,b/=g,c/=g;//除以三个数的 gcd
    if(a==0||b==0||c==0)//特判
    {
        cout<<"No!!!"<<endl;
        return 0;
    }
    dfs(1);//全排列
    if(!f)
        cout<<"No!!!"<<endl;
    return 0;
}

B

题意

给定一个 \(n\times n\) 的 01 矩阵,可以把最多一个 \(0\) 变成 \(1\),求操作后能获得的最大四连通块大小。

题解

跑个 dfs 给每个连通块染个色并统计大小,然后枚举每个 \(0\),找到和这个格子相邻的连通块并累加大小,最后取最大值就行了。

代码:

#define ll long long
const int _mxn=500+5;
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
int n,a[_mxn][_mxn];
int vis[_mxn][_mxn];//染色数组
int ans=0;
void dfs(int x,int y,int s)
{
    vis[x][y]=s;
    for(int i=0;i<4;i++)
    {
        int tx=x+dx[i],ty=y+dy[i];
        if(tx>=1&&tx<=n&&ty>=1&&ty<=n&&!vis[tx][ty]&&a[tx][ty])
            dfs(tx,ty,s);
    }
}
int cnt[_mxn*_mxn],s=1;//cnt[k] 为 k 色的连通块大小
bool flag[_mxn*_mxn];
void solve()//预处理染色
{
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(a[i][j]&&!vis[i][j])
                dfs(i,j,s),s++;
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cnt[vis[i][j]]++;//计数
    for(int i=1;i<=s;i++)//取一下最大值
        ans=max(ans,cnt[i]);
}
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>a[i][j];
    solve();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(!a[i][j])//为零
            {
                memset(flag,false,sizeof(flag));
                int res=1;//这一个格子也要加上
                for(int d=0;d<4;d++)//四个方向查找
                {
                    int tx=i+dx[d],ty=j+dy[d];
                    if(tx>=1&&tx<=n&&ty>=1&&ty<=n&&a[tx][ty])
                    {
                        if(!flag[vis[tx][ty]])
                        {
                            res+=cnt[vis[tx][ty]];
                            flag[vis[tx][ty]]=true;//标一下防止重复加
                        }
                    }
                }
                ans=max(ans,res);
            }
        }
    cout<<ans<<endl;
    return 0;
}

C

题意

给定一个 \(n\times m\) 的由 FR 组成的字符矩阵,求最大的 F 正方形的面积。

题解

去年做过类似的(Day 2 B),只不过是长方形。同样的方法,求答案时长宽取较小值平方就行了。

采用悬线法,设 \(h_{i,j}\)表示以 \((i,j)\) 为下端点的悬线的最长长度,\(l_{i,j},r_{i,j}\) 分别为悬线有该长度时能向左和向右的距离。答案即为 \(\max(\min(h_{i,j},(r_{i,j}−l_{i,j}+1))^2)\)

代码:

#define ll long long
const int _mxn=5000+5;
int n,m;
bool a[_mxn][_mxn];
int h[_mxn][_mxn],l[_mxn][_mxn],r[_mxn][_mxn];//
int main()
{
    ___();
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            char c;
            cin>>c;
            a[i][j]=(c=='F'?1:0);//改一下存储省点空间,不然 MLE
            h[i][j]=1,l[i][j]=r[i][j]=j;
        }
        for(int j=2;j<=m;j++)//当悬线长 1 时处理 l[i][j] 和 r[i][j]
            if(a[i][j]&&a[i][j-1])
                l[i][j]=l[i][j-1];
        for(int j=m-1;j>=1;j--)
            if(a[i][j]&&a[i][j+1])
                r[i][j]=r[i][j+1];
    }
    int ans=0;
    for(int i=1;i<=n;i++)//处理 h[i][j] 和对应 l[i][j] 和 r[i][j]
    {
        for(int j=1;j<=m;j++)
        {
            if(a[i][j])
            {
                if(a[i-1][j])
                {
                    h[i][j]=h[i-1][j]+1;
                    l[i][j]=max(l[i][j],l[i-1][j]);
                    r[i][j]=min(r[i][j],r[i-1][j]);
                }
                ans=max(ans,min((r[i][j]-l[i][j]+1),h[i][j])*min((r[i][j]-l[i][j]+1),h[i][j]));//求答案
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

D

题意

给定一个 \(a\times b\) 的非负整数矩阵,求每个 \(n\times n\) 的区域中极差的最小值。

题解

单调队列预处理每一行长度为 \(n\) 的区间中的极值,然后枚举左上角,往下 \(n\) 行找到总极值,最后求答案,时间复杂度 \(O(abn)\)

代码:

#define ll long long
const int _mxn=1000+5;
int a,b,n,t[_mxn][_mxn],mx[_mxn][_mxn],mn[_mxn][_mxn];
int main()
{
    ___();
    cin>>a>>b>>n;
    for(int i=1;i<=a;i++)//读入 & 预处理
    {
        deque<int> qn,qx;
        for(int j=1;j<=b;j++)
        {
            cin>>t[i][j];

            while(!qn.empty()&&t[i][qn.back()]>=t[i][j])
                qn.pop_back();
            while(!qn.empty()&&j-qn.front()>=n)
                qn.pop_front();
            qn.push_back(j);

            while(!qx.empty()&&t[i][qx.back()]<=t[i][j])
                qx.pop_back();
            while(!qx.empty()&&j-qx.front()>=n)
                qx.pop_front();
            qx.push_back(j);

            if(j>=n)
            {
                mn[i][j]=t[i][qn.front()];//第 i 行 [j-n+1,j] 最小值
                mx[i][j]=t[i][qx.front()];//第 i 行 [j-n+1,j] 最大值
            }
        }
    }
    int ans=1e9;
    for(int i=1;i+n-1<=a;i++)//枚举左上角
    {
        for(int j=n;j<=b;j++)
        {
            int mnt=1e9,mxt=0;
            for(int k=i;k<i+n;k++)//往下 n 行
            {
                mnt=min(mnt,mn[k][j]);//区域内最小值
                mxt=max(mxt,mx[k][j]);//区域内最大值
            }
            ans=min(ans,mxt-mnt);
        }
    }
    cout<<ans<<endl;
    return 0;
}

E

题意

对于任一长度为 \(n\) 的数组 \(a\),定义 \(\operatorname{cost}(a)=\sum_{i=1}^{n}\operatorname{mex}\{[a_1,a_2,\cdots,a_i]\}\),其中 \(\operatorname{mex}\) 指最小的集合中未出现的非负整数。

给定一个集合 \(\{0,1,2,\cdots,n−1\}\) 的排列 \(p\),求所有循环移位后的排列 \(p'\) 中,\(\operatorname{cost}(p')\)的最大值。其中循环移位指将数组任意长度(可以为 \(0\))的前缀移到最后。

题解

老师的 std:

#include<bits/stdc++.h>
using namespace std;
#define MAX 250005
int n,a[MAX];
bool vis[MAX];
int mex[MAX];
#define ll long long
ll ans,cost;
int st[MAX],head,tail;
void sol(){
    cin>>n;
    for(int i=0;i<=n+1;i++) vis[i]=0;
    int nw=0;
    head=1,tail=0;
    cost=0; 
    for(int i=1;i<=n;i++){
        cin>>a[i];vis[a[i]]=1;
        while(vis[nw]) nw++;
        mex[i]=nw;cost+=mex[i];
        //记录初始状态的前缀mex和cost值
        while(tail&&mex[st[tail]]==mex[i]) tail--;
        //前缀mex满足单调不降,可以用单调容器来储存分界点
        st[++tail]=i;
    }
    ans=cost; 
    for(int i=1;i<n;i++){ //不断的把第一个数移动到最后
        cost-=mex[st[head]];//先删除掉第一个数的贡献
        if(st[head]==i) head++; //如果队头被删除了,那么要出队
        int c=a[i];
        // st[head-1]=i;
        
        while(head<=tail&&mex[st[tail]]>c){ //那些mex大于c的前缀,mex都会被修改成c
            cost-=1ll*mex[st[tail]]*(st[tail]-st[tail-1]);
            //先减去这些位置对cost的贡献
            tail--;
        }

        st[++tail]=n+i-1; mex[st[tail]]=c;
        //产生了新的分界点,该点的mex值是c
        cost+=1ll*c*(st[tail]-st[tail-1]);
        //把这一段mex值为c的贡献全部加上
        mex[n+i]=n; st[++tail]=n+i; cost+=n;
        //最后一个位置的mex值为n,单独考虑
    
        ans=max(ans,cost);

        // for(int j=i+1;j<=n+i;j++)
        //         cout<<mex[j]<<" ";
        //     cout<<endl;
    }
    cout<<ans<<"\n";
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t=1;cin>>t;
    while(t--) sol();
}
//g++ a.cpp -o a && a < in.txt > out.txt

Day 3

树状数组

#define ll long long
inline int lowbit(int x){return x&-x;}
struct BIT
{
    ll tr[_mxn];
    void add(int x,ll k)
    {
        for(int i=x;i<_mxn;i+=lowbit(i))
            tr[i]+=k;
    }
    ll query(int l,int r)
    {
        ll res=0;
        for(int i=r;i>0;i-=lowbit(i))
            res+=tr[i];
        for(int i=l-1;i>0;i-=lowbit(i))
            res-=tr[i];
        return res;
    }
}tr;

二维树状数组

#define ll long long
inline int lowbit(int x){return x&-x;}
struct BIT
{
    ll tr[_mxn][_mxn];
    void add(int x,int y,ll k)
    {
        for(int i=x;i<_mxn;i+=lowbit(i))
            for(int j=y;j<_mxn;j+=lowbit(j))
                tr[i][j]+=k;
    }
    ll query(int x1,int y1,int x2,int y2)
    {
        if(x1>x2)
            swap(x1,x2);
        if(y1>y2)
            swap(y1,y2);
        ll res=0;
        for(int i=x2;i>0;i-=lowbit(i))
            for(int j=y2;j>0;j-=lowbit(j))
                res+=tr[i][j];
        for(int i=x2;i>0;i-=lowbit(i))
            for(int j=y1-1;j>0;j-=lowbit(j))
                res-=tr[i][j];
        for(int i=x1-1;i>0;i-=lowbit(i))
            for(int j=y2;j>0;j-=lowbit(j))
                res-=tr[i][j];
        for(int i=x1-1;i>0;i-=lowbit(i))
            for(int j=y1-1;j>0;j-=lowbit(j))
                res+=tr[i][j];
        return res;
    }
}tr;

Day 3 模拟赛

A

题意

给定一个正整数 \(x(1\le x\le10^{100000})\),把这个数字分成两个非空子串,求两个子串代表数字和为偶数的分割方案数。

题解

暴力枚举分割点,然后加两个串最后一位,判断。

代码:

#define ll long long
const int _mxn=+5;
string s;
int main()
{
    ___();
    cin>>s;
    int ans=0;
    for(int i=0;i<s.size()-1;i++)
    {
        if((s[i]+s[s.size()-1]-'0'-'0')%2==0)
            ans++;
    }
    cout<<ans<<endl;
    return 0;
}

B

题意

给定一个 \(n\times m\) 的仅包含 \(0\sim5\) 的整数的矩阵,求最大的不与 \(0\) 或边界相邻的同数字四连通块面积。

题解

先把所有和 \(0\) 或边界相邻的连通块染成 \(0\),然后把不同连通块染成不同色,计算并取最大值即可。

代码:

#define ll long long
const int _mxn=500+5;
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
int n,m;
int a[_mxn][_mxn];
int t[_mxn][_mxn],tot=0,cnt[_mxn*_mxn];
void dfs(int x,int y,int nw,int s)
{
    t[x][y]=s;
    for(int i=0;i<4;i++)
    {
        int tx=x+dx[i],ty=y+dy[i];
        if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&a[tx][ty]==nw&&t[tx][ty]!=s)
            dfs(tx,ty,nw,s);
    }
}
int main()
{
    ___();
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            char c;
            cin>>c;
            a[i][j]=t[i][j]=c-'0';
        }
    for(int i=0;i<=n+1;i++)//和 0 或边界相邻的染成 0
    {
        for(int j=0;j<=m+1;j++)
        {
            if(a[i][j]==0)
            {
                for(int d=0;d<4;d++)
                {
                    int tx=i+dx[d],ty=j+dy[d];
                    if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&a[tx][ty])
                        dfs(tx,ty,a[tx][ty],0);
                }
            }
        }
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            a[i][j]=t[i][j],t[i][j]=0;
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(a[i][j]&&!t[i][j])//不同连通块染成不同色
                dfs(i,j,a[i][j],++tot);
        }
    }
    for(int i=1;i<=n;i++)//统计每个颜色格子数
        for(int j=1;j<=m;j++)
            cnt[t[i][j]]++;
    for(int i=1;i<=tot;i++)//求答案
        ans=max(ans,cnt[i]);
    cout<<ans<<endl;
    return 0;
}

C

题意

给定 \(n(1\le n\le10^{5})\) 个点,每个点等级定义为横纵坐标均小于等于该点横纵坐标的点个数(不算该点),分别求 \(0\sim(n-1)\) 等级的点数量。点按纵坐标升序给出,纵坐标相等按横坐标升序给出。

题解

经典二维偏序。\(n\) 太大了,不能直接用二维树状数组。发现如果按照给出顺序,只需要用一维树状数组维护两个坐标,然后取前缀和较小值即可。最好加个离散化。

代码:

#define ll long long
const int _mxn=1e5+5,_mxx=1e6+5;
inline int lowbit(int x){return x&-x;}
struct BIT
{
    ll tr[_mxn];
    void add(int x,ll k)
    {
        for(int i=x;i<_mxn;i+=lowbit(i))
            tr[i]+=k;
    }
    ll query(int l,int r)
    {
        ll res=0;
        for(int i=r;i>0;i-=lowbit(i))
            res+=tr[i];
        for(int i=l-1;i>0;i-=lowbit(i))
            res-=tr[i];
        return res;
    }
}tx,ty;
int n,x[_mxn],y[_mxn],ans[_mxn];
int ttx[_mxn],tty[_mxn],mx[_mxx],my[_mxx];
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>x[i]>>y[i];
        ttx[i]=x[i],tty[i]=y[i];
    }
    //离散化
    sort(ttx+1,ttx+n+1);
    int nx=unique(ttx+1,ttx+n+1)-ttx-1;
    int ny=unique(tty+1,tty+n+1)-tty-1;
    for(int i=1;i<=nx;i++)
        mx[ttx[i]]=i;
    for(int i=1;i<=ny;i++)
        my[tty[i]]=i;
    for(int i=1;i<=n;i++)
        x[i]=mx[x[i]],y[i]=my[y[i]];
    //求答案
    for(int i=1;i<=n;i++)
    {
        tx.add(x[i],1),ty.add(y[i],1);
        ans[min(tx.query(1,x[i]),ty.query(1,y[i]))]++;
    }
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<endl;
    return 0;
}

D

题意

给定长为 \(n\) 的序列 \(a\),求满足 \(1\le i<j<k\le n\)\(a_i<a_j<a_k\) 的三元组 \((i,j,k)\) 个数。

题解

类似逆序对,求出每个数左侧比它小的数个数和右侧比它大的数个数,两个乘起来并累加就是答案。

代码:

#define ll long long
const int _mxn=30000+5,_mxa=1e9+5;
inline int lowbit(int x){return x&-x;}
struct BIT
{
    map<int,ll> tr;//暴力 map 存储
    void add(int x,ll k)
    {
        for(int i=x;i<_mxa;i+=lowbit(i))
            tr[i]+=k;
    }
    ll query(int l,int r)
    {
        ll res=0;
        for(int i=r;i>0;i-=lowbit(i))
            res+=tr[i];
        for(int i=l-1;i>0;i-=lowbit(i))
            res-=tr[i];
        return res;
    }
}tr1,tr2;
int n,a[_mxn];
ll l[_mxn],r[_mxn];
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        tr1.add(a[i],1);
        l[i]=tr1.query(1,a[i]-1);
    }
    for(int i=n;i>=1;i--)
    {
        tr2.add(a[i],1);
        r[i]=tr2.query(a[i]+1,1e9);
    }
    ll ans=0;
    for(int i=1;i<=n;i++)
        ans+=l[i]*r[i];
    cout<<ans<<endl;
    return 0;
}

E

题意

给定长为 \(n\) 的序列 \(a\),定义 \(f(l,r,x)=\sum_{i=l}^{r}[a_i=x](1\le l\le r\le n)\),其中 \([]\)艾佛森括号,其中条件满足为 \(1\),否则为 \(0\)。求满足 \(1\le i<j\le n\)\(f(1,i,a_i)>f(j,n,a_j)\) 的二元组 \((i,j)\) 个数。

题解

先离散化一下。可以用树状数组求 \(f(1,i,a_i),f(j,n,a_j)\) 的值,还可以用类似逆序对的思路直接求出答案。具体看代码:

#define ll long long
const int _mxn=1e6+5,_mxa=1e9+5;
inline int lowbit(int x){return x&-x;}
struct BIT
{
    ll tr[_mxn];
    void add(int x,ll k)
    {
        for(int i=x;i<_mxn;i+=lowbit(i))
            tr[i]+=k;
    }
    ll query(int l,int r)
    {
        ll res=0;
        for(int i=r;i>0;i-=lowbit(i))
            res+=tr[i];
        for(int i=l-1;i>0;i-=lowbit(i))
            res-=tr[i];
        return res;
    }
}tri,trj,tr;
int n,a[_mxn],t[_mxn];
map<int,int> ma;
int fi[_mxn],fj[_mxn];
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        t[i]=a[i];
    }
    //离散化
    sort(t+1,t+n+1);
    int nt=unique(t+1,t+n+1)-t-1;
    for(int i=1;i<=nt;i++)
        ma[t[i]]=i;
    for(int i=1;i<=n;i++)
        a[i]=ma[a[i]];
    for(int i=1;i<=n;i++)
    {
        tri.add(a[i],1);
        fi[i]=tri.query(a[i],a[i]);//前面的 a[i] 个数
    }
    for(int j=n;j>=1;j--)
    {
        trj.add(a[j],1);
        fj[j]=trj.query(a[j],a[j]);//后面的 a[j] 个数
    }
    ll ans=0;
    // for(int i=1;i<=n;i++)//暴力
    //     for(int j=i+1;j<=n;j++)
    //         if(fi[i]>fj[j])
    //             ans++;
    for(int i=n;i>=1;i--)
    {
        ans+=tr.query(1,fi[i]-1);//后面的比 fi[i] 小的 fj[] 的个数
        tr.add(fj[i],1);
    }
    cout<<ans<<endl;
    return 0;
}
/*
i<j,fi[i]>fj[j]
*/

Day 4

线段树

const int _mxn=1e5+5;
int n,a[_mxn];
struct segtree
{
    struct node
    {
        int l,r;
        ll dat,add;
        int len(){return r-l+1;}
    }tr[_mxn<<2];
    inline int ls(int p){return p<<1;}
    inline int rs(int p){return p<<1|1;}
    void pushup(int p)
    {
        tr[p].dat=tr[ls(p)].dat+tr[rs(p)].dat;
    }
    void build(int p,int l,int r)
    {
        tr[p].l=l,tr[p].r=r;
        tr[p].add=0;
        if(l==r)
        {
            tr[p].dat=a[l];
            return;
        }
        int mid=(l+r)>>1;
        build(ls(p),l,mid);
        build(rs(p),mid+1,r);
        pushup(p);
    }
    void pushdown(int p)
    {
        tr[ls(p)].dat+=tr[p].add*tr[ls(p)].len();
        tr[ls(p)].add+=tr[p].add;
        tr[rs(p)].dat+=tr[p].add*tr[rs(p)].len();
        tr[rs(p)].add+=tr[p].add;
        tr[p].add=0;
    }
    void update(int p,int l,int r,ll k)
    {
        if(l<=tr[p].l&&tr[p].r<=r)
        {
            tr[p].dat+=k*tr[p].len();
            tr[p].add+=k;
            return;
        }
        pushdown(p);
        int mid=(tr[p].l+tr[p].r)>>1;
        if(l<=mid)
            update(ls(p),l,r,k);
        if(r>mid)
            update(rs(p),l,r,k);
        pushup(p);
    }
    ll query(int p,int l,int r)
    {
        if(l<=tr[p].l&&tr[p].r<=r)
            return tr[p].dat;
        pushdown(p);
        int mid=(tr[p].l+tr[p].r)>>1;
        ll res=0;
        if(l<=mid)
            res+=query(ls(p),l,r);
        if(r>mid)
            res+=query(rs(p),l,r);
        return res;
    }
}tr;

Day 4 模拟赛

A

题意

给定一个边权全为 \(1\) 的有向图,求 \(1\) 号点到每个点的最短路,有点到不了输出 \(-1\)

题解

写个 bfs 即可。

代码:

#define ll long long
const int _mxn=1e5+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n,m,ans[_mxn];
bool vis[_mxn];
void bfs()
{
    queue<int> q;
    q.push(1);
    vis[1]=true;
    ans[1]=0;
    while(!q.empty())
    {
        int nw=q.front();q.pop();
        for(int it:g[nw])
        {
            if(!vis[it])
            {
                q.push(it);
                vis[it]=true;
                ans[it]=ans[nw]+1;
            }
        }
    }
}
int main()
{
    ___();
    cin>>n>>m;
    while(m--)
    {
        int u,v;
        cin>>u>>v;
        add(u,v);
    }
    memset(ans,0x3f,sizeof(ans));
    bfs();
    for(int i=1;i<=n;i++)
        cout<<(ans[i]==0x3f3f3f3f?-1:ans[i])<<" ";
    cout<<endl;
    return 0;
}

B

题意

\(a\) 颗红色种子,\(b\) 颗蓝色,\(c\) 颗绿色,\(x\) 颗红色可以转化为 \(1\) 颗蓝色,\(y\) 颗蓝色可以转化为 \(1\) 颗绿色,只能单向转化。一个套装包含三种种子各 \(1\) 颗,求最多可以凑成的套装数。

题解

二分答案。check 写法:绿不够用蓝凑,蓝不够用红凑,最后判红够不够即可。

代码:

#define ll long long
const int _mxn=+5;
ll a,b,c,x,y;
bool check(ll mid)
{
    ll ta=a,tb=b,tc=c;
    tb-=max(mid-tc,0ll)*y;
    ta-=max(mid-tb,0ll)*x;
    return ta>=mid;
}
int main()
{
    ___();
    int _;
    cin>>_;
    while(_--)
    {
        cin>>a>>b>>c>>x>>y;
        ll l=0,r=1e9,ans;
        while(l<=r)
        {
            ll mid=(l+r)>>1;
            if(check(mid))
                l=mid+1,ans=mid;
            else
                r=mid-1;
        }
        cout<<ans<<endl;
    }
    return 0;
}

C

题意

给定一个序列,要求支持区间异或上一个数和求区间和。

题解

必定是使用线段树,但是区间异或不能直接维护。可以用线段树 tr[p].dat[k]\(p\) 号节点区间里数二进制第 \(k\)\(1\) 的个数,修改时遍历参数的二进制位,为 \(1\) 就取反区间里数的对应位,\(1\) 的个数就变成了原本 \(0\) 的个数,即 tr[p].dat[k]=tr[p].len-tr[p].dat[k]。求答案时枚举位,累加 \(1\) 的个数乘上对应位的权值(\(2^k\))即可。

代码:

#define ll long long
const int _mxn=1e5+5,_mxa=1e6;
int n;
ll a[_mxn];
struct segtree
{
    struct node
    {
        int l,r;
        ll dat[22];
        ll tag;
        int len(){return r-l+1;}
    }tr[_mxn<<2];//tr[i].dat[k]: i 节点的区间中数二进制第 k 位 1 的个数
    inline int ls(int p){return p<<1;}
    inline int rs(int p){return p<<1|1;}
    void pushup(int p)
    {
        for(int k=0;k<21;k++)
            tr[p].dat[k]=tr[ls(p)].dat[k]+tr[rs(p)].dat[k];
    }
    void build(int p,int l,int r)
    {
        tr[p].l=l,tr[p].r=r;
        tr[p].tag=0;
        if(l==r)
        {
            for(int k=0;k<21;k++)
                tr[p].dat[k]=(a[l]>>k&1);
            return;
        }
        int mid=(l+r)>>1;
        build(ls(p),l,mid);
        build(rs(p),mid+1,r);
        pushup(p);
    }
    void pushdown(int p)
    {
        for(int k=0;k<21;k++)
            if(tr[p].tag>>k&1)
            {
                tr[ls(p)].dat[k]=tr[ls(p)].len()-tr[ls(p)].dat[k];
                tr[rs(p)].dat[k]=tr[rs(p)].len()-tr[rs(p)].dat[k];
            }
        tr[ls(p)].tag^=tr[p].tag;
        tr[rs(p)].tag^=tr[p].tag;
        tr[p].tag=0;
    }
    void update(int p,int l,int r,ll x)
    {
        if(l<=tr[p].l&&tr[p].r<=r)
        {
            for(int k=0;k<21;k++)
                if(x>>k&1)
                    tr[p].dat[k]=tr[p].len()-tr[p].dat[k];
            tr[p].tag^=x;
            return;
        }
        pushdown(p);
        int mid=(tr[p].l+tr[p].r)>>1;
        if(l<=mid)
            update(ls(p),l,r,x);
        if(r>mid)
            update(rs(p),l,r,x);
        pushup(p);
    }
    ll query(int p,int l,int r)
    {
        if(l<=tr[p].l&&tr[p].r<=r)
        {
            ll res=0;
            for(int k=0;k<21;k++)
                res+=(1<<k)*tr[p].dat[k];
            return res;
        }
        pushdown(p);
        int mid=(tr[p].l+tr[p].r)>>1;
        ll res=0;
        if(l<=mid)
            res+=query(ls(p),l,r);
        if(r>mid)
            res+=query(rs(p),l,r);
        return res;
    }
}tr;

int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    tr.build(1,1,n);
    int m;
    cin>>m;
    while(m--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int l,r;
            cin>>l>>r;
            cout<<tr.query(1,l,r)<<endl;
        }
        else
        {
            int l,r;
            ll x;
            cin>>l>>r>>x;
            tr.update(1,l,r,x);
        }
    }
    return 0;
}

D

题意

给定 \(n,m\)\(n\) 条通道,通过第 \(i\) 条可以在 \([l_i,r_i]\) 中的任意两个坐标间传送,通道能量值为 \(w_i\)。选出一部分通道(可以全选),使可以通过这些通道从 \(1\)\(m\),代价为这些通道最大能量值和最小能量值之差,求满足要求需要的最小代价。

题解(思路来自老师)

考虑枚举使用到的通道的最大能量 \(y\),假设通车时使用到的最小能量为 \(x\),那么必须满足 \(1\)\(m\) 的每个坐标点都被一条能量在 \([x,y]\) 中的通道覆盖。发现对每个点只需记录覆盖了他的通道的最大能量,因为能量更小的与 \(y\) 差值一定更大。用线段树来维护最大能量,加入一条通道相当于给一个区间对这个通道能量取最大值。最小能量 \(x\) 即为全局最小值。

代码:

#define ll long long
const int _mxn=3e5+5,_mxm=1e6+5;
struct segtree
{
    struct node
    {
        int l,r;
        ll dat,tag;
        int len(){return r-l+1;}
    }tr[_mxm<<2];
    inline int ls(int p){return p<<1;}
    inline int rs(int p){return p<<1|1;}
    void pushup(int p)
    {
        tr[p].dat=min(tr[ls(p)].dat,tr[rs(p)].dat);
    }
    void build(int p,int l,int r)
    {
        tr[p].l=l,tr[p].r=r;
        tr[p].tag=0;
        if(l==r)
        {
            tr[p].dat=0;
            return;
        }
        int mid=(l+r)>>1;
        build(ls(p),l,mid);
        build(rs(p),mid+1,r);
        pushup(p);
    }
    void pushdown(int p)
    {
        tr[ls(p)].dat=max(tr[ls(p)].dat,tr[p].tag);
        tr[ls(p)].tag=max(tr[ls(p)].tag,tr[p].tag);
        tr[rs(p)].dat=max(tr[rs(p)].dat,tr[p].tag);
        tr[rs(p)].tag=max(tr[rs(p)].tag,tr[p].tag);
    }
    void update(int p,int l,int r,ll k)
    {
        if(l<=tr[p].l&&tr[p].r<=r)
        {
            tr[p].dat=max(tr[p].dat,k);
            tr[p].tag=max(tr[p].tag,k);
            return;
        }
        pushdown(p);
        int mid=(tr[p].l+tr[p].r)>>1;
        if(l<=mid)
            update(ls(p),l,r,k);
        if(r>mid)
            update(rs(p),l,r,k);
        pushup(p);
    }
}tr;
struct node
{
    int l,r,w;
}a[_mxn];
bool operator<(node _x,node _y)
{
    return _x.w<_y.w;
}
int n,m;
int main()
{
    ___();
    cin>>n>>m;
    tr.build(1,1,m-1);
    for(int i=1;i<=n;i++)
        cin>>a[i].l>>a[i].r>>a[i].w;
    sort(a+1,a+n+1);//按 w 升序排序
    ll ans=1e9;
    for(int i=1;i<=n;i++)
    {
        a[i].r--;
        tr.update(1,a[i].l,a[i].r,a[i].w);
        if(tr.tr[1].dat!=0)//开头有了
            ans=min(ans,a[i].w-tr.tr[1].dat);
    }
    cout<<ans<<endl;
    return 0;
}

E

题意

给定两个长为 \(n\) 的序列 \(a,b\),求满足 \(1\le l\le r\le n\)\([l,r]\) 区间内 \(a\) 的最大值等于 \(b\) 的最小值的 \((l,r)\) 数对个数。

题解(思路来自老师)

打个 ST 表维护区间最大最小值,然后枚举右端点 \(r\)。注意到最大减最小是单调不降的,所以可以二分出区间最大减最小为 \(0\)\(l\) 的最大和最小值,然后就可以求答案了。

代码咕咕咕,因为二分写假了还没调出来。

Day 5

并查集

struct dsu
{
    int f[_mxn],siz[_mxn];
    void init(int n)
    {
        for(int i=1;i<=n;i++)
            f[i]=i,siz[i]=1;
    }
    int find(int x)
    {
        if(f[x]==x)
            return x;
        return find(f[x]);
    }
    void merge(int x,int y)
    {
        int fx=find(x),fy=find(y);
        if(fx!=fy)
        {
            f[fy]=fx;
            siz[fx]+=siz[fy];
        }
    }
    bool same(int x,int y){return find(x)==find(y);}
    int size(int x){return siz[find(x)];}
};

Kruskal 最小生成树

typedef ll w_type;
struct edge
{
    int u,v;
    w_type w;
    edge(){}
    edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
    bool operator<(edge x) const {return w<x.w;}
    bool operator>(edge x) const {return w>x.w;}
};
vector<edge> e;
void add(int u,int v,w_type w){e.push_back(edge(u,v,w));}
w_type kruskal(int n)
{
    dsu t;
    t.init(n);
    sort(e.begin(),e.end());
    w_type res=0;
    int cnt=0;
    for(edge it:e)
    {
        if(!t.same(it.u,it.v))
        {
            t.merge(it.u,it.v);
            res+=it.w;
            cnt++;
            if(cnt==n-1)
                break;
        }
    }
    if(cnt<n-1)
        return -1;
    return res;
}

Day 5 模拟赛 ACM

A

题意

有三个数 \(a,b,c\),三个线索 \(>\)\(<\),分别表示:

  1. \(a,b\) 大小关系;
  2. \(a,c\) 大小关系;
  3. \(b,c\) 大小关系。

求哪个数最大,若矛盾无法判断输出 \(-1\)

题解

签到题。

套一堆 if 判断即可。代码:(赛时两人一起打的,很乱)

#include<bits/stdc++.h>
using namespace std;
int main()
{
    char a,b,c;
    cin>>a>>b>>c;
    if(a=='>')//a>b
    {
        if(b=='>')//a>c
        {
            cout<<"A"<<endl;//a 比两个都大
        }
        else//a<c
        {//b<c 就 c 比两个都大;b>c 就矛盾
            c=='<'?cout<<"C"<<endl:cout<<-1<<endl;
        }
    }
    else//a<b
    {
        if(b=='>'){//a>c
            if(c=='>')//b>c
                cout<<"B"<<endl;//b 比两个都大
            else
                cout<<-1<<endl;//矛盾
        }
        else//a<c,a 最小
        {
            if(c=='>'){//b>c
                cout<<"B"<<endl;
            }
            else//b<c
                cout<<"C"<<endl;
        }
    }
    return 0;
}

B

题意

给定一个长 \(n\) 的序列 \(a\),可以把整个序列的求和运算(即 \(a_1+a_2+\cdots+a_n\))中的最多一个加号变成乘号,求操作后答案的最大值。

题解

签到题。

暴力枚举变哪个,取最大值即可。记得开 long long。

代码:

#define ll long long
const int _mxn=1e5+5;
int n;
ll a[_mxn],s[_mxn];
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i],s[i]=s[i-1]+a[i];
    ll ans=s[n];//打的有点急懒得改了,直接用前缀和数组算总和了。
    for(int i=2;i<=n;i++)
    {
        ans=max(ans,s[n]-a[i-1]-a[i]+a[i-1]*a[i]);//减掉两个再加上两个的积。
    }
    cout<<ans<<endl;
    return 0;
}

C

题意

\(n\) 个人手上有一些卡牌,有 \(m\) 次操作 \((u,v)\),把 \(u\) 的卡牌给 \(v\) 一张。求经过 \(m\) 次操作后卡牌比原来多的人数。

题解

签到题。

模拟一下就行了,不用管原来有多少。

代码:

#define ll long long
const int _mxn=2e5+5;
int n,a[_mxn],t[_mxn];
int main()
{
    ___();
    int m;
    cin>>n>>m;
    while(m--)
    {
        int u,v;
        cin>>u>>v;
        t[u]--,t[v]++;
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        if(t[i]>a[i])
            ans++;
    cout<<ans<<endl;
    return 0;
}

D

题意

定义字符串的多样性 \(d(s)\) 为字符串 \(s\) 中的不同字符种类数。给定仅包含小写字母的字符串 \(s\),求 \(d(s)\)\(s\) 的子串中 \(d(s')\) 分别为 \(1,2,\cdots,d(s)\) 的子串数量。

题解

双指针。建立一个桶数组存储区间内每个字母出现次数,实时更新多样性。对于每个 \(i\in\{1,2,\cdots,d(s)\}\),枚举区间右端点 \(r\),找到使多样性为 \(i\) 的最小左端点 \(l\),记为 \(f_{r,i}\),答案即为 \(f_{r,i-1}-f{r,i}\)

代码:

#define ll long long
const int _mxn=3e5+5;
string s;
int t[30],k;
int f[_mxn][30];//f[r][j] 表示右端点为 r 时使多样性为 j 的最小 l
int main()
{
    ___();
    cin>>s;
    int n=s.size();
    s=' '+s;
    for(int i=1;i<=n;i++)
    {
        if(!t[s[i]-'a'])
            t[s[i]-'a']++,k++;
        f[i][0]=i+1;
    }
    cout<<k<<endl;
    for(int i=1;i<=k;i++)
    {
        memset(t,0,sizeof(t));
        int sum=0;
        ll ans=0;
        for(int l=1,r=1;r<=n;r++)
        {
            int c=s[r]-'a';
            if(!t[c])
                sum++;
            t[c]++;
            while(sum>i)
            {
                if(t[s[l]-'a']==1)
                    sum--;
                t[s[l]-'a']--;
                l++;
            }
            f[r][i]=l;
            if(sum==i)
                ans+=f[r][i-1]-f[r][i];
        }
        cout<<ans<<endl;
    }
    return 0;
}

E

题意

给定一个 \(n\) 个顶点的加权树。查询 \(m\) 次,第 \(i\) 个查询以整数 \(q_i\) 给出,每次查询需要计算满足 \(u<v\)\(u\to v\) 的简单路径上最大边权 \(\le q_i\) 的顶点对 \((u,v)\) 的数量。

题解

思路不会,直接贴赛后订的代码:

#define ll long long
const int _mxn=2e5+5;
ll p;
struct dsu
{
    int f[_mxn],siz[_mxn];
    void init(int n)
    {
        for(int i=1;i<=n;i++)
            f[i]=i,siz[i]=1;
    }
    int find(int x)
    {
        if(f[x]==x)
            return x;
        return f[x]=find(f[x]);
    }
    void merge(int x,int y)
    {
        int fx=find(x),fy=find(y);
        if(fx!=fy)
        {
            f[fx]=fy;
            p-=1ll*siz[fx]*(siz[fx]-1)/2;
            p-=1ll*siz[fy]*(siz[fy]-1)/2;
            siz[fy]+=siz[fx];
            p+=1ll*siz[fy]*(siz[fy]-1)/2;
        }
    }
    bool same(int x,int y){return find(x)==find(y);}
    int size(int x){return siz[find(x)];}
}st;
int n,m;
struct node
{
    int u,v,w,id;
    node(){}
    node(int _u,int _v,int _w,int _id):u(_u),v(_v),w(_w),id(_id){}
};
vector<node> a;
bool cmp(node _x,node _y)
{
    if(_x.w!=_y.w)
        return _x.w<_y.w;
    return _x.id<_y.id;
}
ll ans[_mxn];
int main()
{
    ___();
    cin>>n>>m;
    st.init(n);
    for(int i=2;i<=n;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        a.push_back(node(u,v,w,0));
    }
    for(int i=1;i<=m;i++)
    {
        int x;
        cin>>x;
        a.push_back(node(0,0,x,i));
    }
    sort(a.begin(),a.end(),cmp);
    for(auto it:a)
    {
        if(it.id)
            ans[it.id]=p;
        else
            st.merge(it.u,it.v);
    }
    for(int i=1;i<=m;i++)
        cout<<ans[i]<<" ";
    cout<<endl;
    return 0;
}

F

题意

给定一个 \(n\times n(1\le n\le 2000)\) 的矩阵,描述的是一棵树的信息,树上每条边都有一个正权重,矩阵的每个数字代表一棵树上两个点之间的路径的权重和。判断这个矩阵是否是一棵树的合法信息。

题解

首先合法一定要 \(d_{i,j}=d_{j,i}\),先判一下这个。把给出的矩阵看成一个完全图的邻接矩阵,显然树是该图的最小生成树,因为图的边权都是正的。然后枚举每两个点,求两点在树上的距离,如果全都等于图上两点间的边权就是对的,反之不对。求距离可以用 LCA,随便找个点作为根,预处理出每个点到根的距离 \(\operatorname{dis}(u)\)\(u\to v\) 的距离即为 \(\operatorname{dis}(u)+\operatorname{dis}(v)-2\times\operatorname{dis}(\operatorname{LCA}(u,v))\)。但是 \(n\) 只有 \(2000\),也可以直接爆搜算距离。

代码:

#define int long long
const int _mxn=2000+5;
struct dsu
{
    int f[_mxn],siz[_mxn];
    void init(int n)
    {
        for(int i=1;i<=n;i++)
            f[i]=i,siz[i]=1;
    }
    int find(int x)
    {
        if(f[x]==x)
            return x;
        return find(f[x]);
    }
    void merge(int x,int y)
    {
        int fx=find(x),fy=find(y);
        if(fx!=fy)
        {
            f[fy]=fx;
            siz[fx]+=siz[fy];
        }
    }
    bool same(int x,int y){return find(x)==find(y);}
    int size(int x){return siz[find(x)];}
}st;
typedef int w_type;
struct graph
{
    struct node
    {
        int v;
        w_type w;
        node(){}
        node(int _v,w_type _w):v(_v),w(_w){}
        bool operator<(node x) const {return w<x.w;}
        bool operator>(node x) const {return w>x.w;}
    };
    struct edge
    {
        int u,v;
        w_type w;
        edge(){}
        edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
        bool operator<(edge x) const {return w<x.w;}
        bool operator>(edge x) const {return w>x.w;}
    };
    vector<node> g[_mxn];
    vector<edge> e;
    void add(int u,int v,w_type w)
    {
        g[u].push_back(node(v,w));
        e.push_back(edge(u,v,w));
    }
}g,tr;
w_type kruskal(int n)
{
    dsu t;
    t.init(n);
    sort(g.e.begin(),g.e.end());
    w_type res=0;
    int cnt=0;
    for(auto it:g.e)
    {
        if(!t.same(it.u,it.v))
        {
            t.merge(it.u,it.v);
            tr.add(it.u,it.v,it.w);
            tr.add(it.v,it.u,it.w);
            res+=it.w;
            cnt++;
            if(cnt==n-1)
                break;
        }
    }
    if(cnt<n-1)
        return -1;
    return res;
}
namespace LCA
{
    int deep[_mxn],f[_mxn][30],lg;
    void init(int u,int fa,int dep)
    {
        deep[u]=dep;
        f[u][0]=fa;
        for(int i=1;i<=lg;i++)
            f[u][i]=f[f[u][i-1]][i-1];
        for(auto it:tr.g[u])
        {
            if(it.v==fa)
                continue;
            init(it.v,u,dep+1);
        }
    }
    int query(int x,int y)
    {
        if(deep[x]<deep[y])
            swap(x,y);
        for(int i=lg;i>=0;i--)
            if(deep[f[x][i]]>=deep[y])
                x=f[x][i];
        if(x==y)
            return x;
        for(int i=lg;i>=0;i--)
            if(f[x][i]!=f[y][i])
                x=f[x][i],y=f[y][i];
        return f[x][0];
    }
};
int n,d[_mxn][_mxn];
int dis[_mxn];
void dfs(int u,int fa)
{
    for(auto it:tr.g[u])
    {
        if(it.v==fa)
            continue;
        dis[it.v]=dis[u]+it.w;
        dfs(it.v,u);
    }
}
signed main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            cin>>d[i][j];
            if(j>i)
                g.add(i,j,d[i][j]);
        }
    for(int i=1;i<=n;i++)
    {
        if(d[i][i])//自己和自己距离不为 0 就不对
        {
            cout<<"NO"<<endl;
            return 0;
        }
        for(int j=i+1;j<=n;j++)
        {
            if(d[i][j]!=d[j][i])
            {
                cout<<"NO"<<endl;
                return 0;
            }
        }
    }
    if(kruskal(n)==-1)//存最小生成树
    {
        cout<<"NO"<<endl;
        return 0;
    }
    LCA::lg=log2(n);//初始化 LCA
    LCA::init(1,0,1);
    dfs(1,0);//初始化 dis
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            if(d[i][j]!=dis[i]+dis[j]-2*dis[LCA::query(i,j)])
            {
                cout<<"NO"<<endl;
                return 0;
            }
        }
    }
    cout<<"YES"<<endl;
    return 0;
}

G

题意

给定一个长为 \(n\) 的由小写字母组成的字符串和 \(q\) 次操作,每个操作有两种:

  • 1 l r d:区间 \(s[l\dots r]\) 中每个字符后移 \(d\) 次。后移:将字符变为字母表的下一个字母;特别地,z 变为 a
  • 2 l r:查询区间 \(s[l\dots r]\) 经过重新排列能否组成回文串。

题解

用线段树维护每个区间每个字母的数量。发现一个字符串如果能重组成回文串,则:

  • 长为奇数:出现次数为奇数的字母数量为 \(1\)
  • 长为偶数:出现次数为奇数的字母数量为 \(0\)

直接计数接判断即可。

代码:

#define ll long long
const int _mxn=1e5+5;
int n;
string s;
struct segtree
{
    struct node
    {
        int l,r;
        int dat[26],add;
        int len(){return r-l+1;}
    }tr[_mxn<<2];//tr[p].dat[k]:p 节点的区间 k 字母的出现次数
    inline int ls(int p){return p<<1;}
    inline int rs(int p){return p<<1|1;}
    void pushup(int p)
    {
        for(int i=0;i<26;i++)
            tr[p].dat[i]=tr[ls(p)].dat[i]+tr[rs(p)].dat[i];
    }
    void build(int p,int l,int r)
    {
        tr[p].l=l,tr[p].r=r;
        tr[p].add=0;
        for(int i=0;i<26;i++)
            tr[p].dat[i]=0;
        if(l==r)
        {
            tr[p].dat[s[l-1]-'a']=1;
            return;
        }
        int mid=(l+r)>>1;
        build(ls(p),l,mid);
        build(rs(p),mid+1,r);
        pushup(p);
    }
    void chg(int p,int d)
    {
        int tmp[26];
        for(int k=0;k<26;k++)
            tmp[k]=tr[p].dat[k];
        for(int k=0;k<26;k++)
            tr[p].dat[(k+d%26)%26]+=tmp[k];//后移 d 次后对应的字母出现次数加原字母出现次数
        for(int k=0;k<26;k++)
            tr[p].dat[k]-=tmp[k];//移完原来的减掉
        (tr[p].add+=d%26)%=26;
    }
    void pushdown(int p)
    {
        chg(ls(p),tr[p].add);
        chg(rs(p),tr[p].add);
        tr[p].add=0;//一定要清零 tag,不然爆零两行泪
    }
    void update(int p,int l,int r,int d)
    {
        if(l<=tr[p].l&&tr[p].r<=r)
        {
            chg(p,d);
            return;
        }
        pushdown(p);
        int mid=(tr[p].l+tr[p].r)>>1;
        if(l<=mid)
            update(ls(p),l,r,d);
        if(r>mid)
            update(rs(p),l,r,d);
        pushup(p);
    }
    vector<int> query(int p,int l,int r)
    {
        if(l<=tr[p].l&&tr[p].r<=r)
        {
            vector<int> res;
            for(int k=0;k<26;k++)
                res.push_back(tr[p].dat[k]);
            return res;
        }
        pushdown(p);
        int mid=(tr[p].l+tr[p].r)>>1;
        vector<int> res(26);
        if(l<=mid)
        {
            vector<int> lans=query(ls(p),l,r);
            for(int i=0;i<26;i++)
                res[i]+=lans[i];
        }
        if(r>mid)
        {
            vector<int> rans=query(rs(p),l,r);
            for(int i=0;i<26;i++)
                res[i]+=rans[i];
        }
        return res;
    }
}tr;
int main()
{
    ___();
    int _;
    cin>>_;
    while(_--)
    {
        int n,q;
        cin>>n>>q;
        cin>>s;
        tr.build(1,1,n);
        while(q--)
        {
            int op;
            cin>>op;
            if(op==1)
            {
                int l,r,d;
                cin>>l>>r>>d;
                tr.update(1,l,r,d%26);
            }
            else
            {
                int l,r;
                cin>>l>>r;
                int len=r-l+1,cnto=0;
                vector<int> res;
                res=tr.query(1,l,r);
                for(int i=0;i<26;i++)
                {
                    if(res[i]%2==1)
                        cnto++;
                }
                if(len%2==cnto)//判断
                    cout<<"Yes"<<endl;
                else
                    cout<<"No"<<endl;
            }
        }
    }
    return 0;
}

H

题意

维护一行 \(n\) 个花盆,共有 \(m\) 个事件,事件有两种:

  1. 园丁在 \([l,r]\) 区间种下新品种花苗;
  2. 询问在 \([l,r]\) 区间内,有多少种不同的花苗。

一个花盆可以种多株花苗。

题解

不会思路,直接贴赛后订的代码:

#define ll long long
const int _mxn=50000+5;
inline int lowbit(int x){return x&-x;}
struct BIT
{
    ll tr[_mxn];
    void add(int x,ll k)
    {
        for(int i=x;i<_mxn;i+=lowbit(i))
            tr[i]+=k;
    }
    ll query(int l,int r)
    {
        ll res=0;
        for(int i=r;i>0;i-=lowbit(i))
            res+=tr[i];
        for(int i=l-1;i>0;i-=lowbit(i))
            res-=tr[i];
        return res;
    }
}trl,trr;
int n,m;
int main()
{
    ___();
    cin>>n>>m;
    while(m--)
    {
        int op,l,r;
        cin>>op>>l>>r;
        if(op==1)
        {
            trl.add(l,1);
            trr.add(r,1);
        }
        else
        {
            cout<<trl.query(1,r)-trr.query(1,l-1)<<endl;
        }
    }
    return 0;
}

Day 6

ST 表

struct ST
{
    int fmx[_mxn][30],fmn[_mxn][30];
    void initmax(int *a)
    {
        for(int i=1;i<=n;i++)
            fmx[i][0]=a[i];
        int t=log2(n);
        for(int j=1;j<=t;j++)
            for(int i=1;i<=n-(1<<j)+1;i++)
                fmx[i][j]=max(fmx[i][j-1],fmx[i+(1<<(j-1))][j-1]);
    }
    void initmin(int *a)
    {
        for(int i=1;i<=n;i++)
            fmn[i][0]=a[i];
        int t=log2(n);
        for(int j=1;j<=t;j++)
            for(int i=1;i<=n-(1<<j)+1;i++)
                fmn[i][j]=min(fmn[i][j-1],fmn[i+(1<<(j-1))][j-1]);
    }
    void init(int *a)
    {
        initmax(a);
        initmin(a);
    }
    int qmax(int l,int r)
    {
        int t=log2(r-l+1);
        return max(fmx[l][t],fmx[r-(1<<t)+1][t]);
    }
    int qmin(int l,int r)
    {
        int t=log2(r-l+1);
        return min(fmn[l][t],fmn[r-(1<<t)+1][t]);
    }
}st;

LCA

namespace LCA
{
    int deep[_mxn],f[_mxn][30],lg;
    void init(int u,int fa,int dep)
    {
        deep[u]=dep;
        f[u][0]=fa;
        for(int i=1;i<=lg;i++)
            f[u][i]=f[f[u][i-1]][i-1];
        for(auto it:g[u])
        {
            if(it==fa)
                continue;
            init(it,u,dep+1);
        }
    }
    int query(int x,int y)
    {
        if(deep[x]<deep[y])
            swap(x,y);
        for(int i=lg;i>=0;i--)
            if(deep[f[x][i]]>=deep[y])
                x=f[x][i];
        if(x==y)
            return x;
        for(int i=lg;i>=0;i--)
            if(f[x][i]!=f[y][i])
                x=f[x][i],y=f[y][i];
        return f[x][0];
    }
}

Day 6 模拟赛

A

题意

\(n\) 颗山楂,第 \(i\) 颗山楂的编号为 \(a_i\),从第 \(1\) 颗到第 \(n\) 颗山楂的顺序开始串糖葫芦,新串上的山楂会串到上一颗山楂的后面。如果糖葫芦山楂数大于 \(1\) 且两端山楂编号相同,这串糖葫芦就串好了,之后重新开始串一串新的。求串完所有山楂能串好多少串糖葫芦。

题解

用一个指针标记开头然后模拟即可。代码:

const int _mxn=100000+5;
int n,a[_mxn];
int main()
{
    ___();
    cin>>n;
    int ans=0,l=1;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(i!=l&&a[i]==a[l])
            ans++,l=i+1;
    }
    cout<<ans<<endl;
    return 0;
}

B

题意

\(n\) 个星球,每个星球有一个能量频率 \(a_i\)。初始飞船在 \(1\) 号星球,所有星球都未激活。激活 \(i\) 号星球要消耗 \(a_i\) 点能量,飞船可以不消耗能量在已激活星球间任意移动,或消耗 \(\operatorname{lcm}(a_i,a_j)\) 能量从已激活星球 \(i\) 到未激活星球 \(j\)。求激活所有星球需要的最少能量。

题解

首先答案肯定包含激活所有星球需要的能量。考虑建图,显然只要图连通,边权和即为移动消耗的能量。建成一个完全图,连接 \((u,v)\) 点的边权是 \(\operatorname{lcm}(a_u,a_v)\),那么答案即为该图的最小生成树的边权和。用 Kruskal 或 Prim 均可,我写的 Kruskal。

代码:

#define ll long long
const int _mxn=2e3+5;
int n,a[_mxn];
int lcm(int x,int y){return x*y/__gcd(x,y);}
struct graph
{
    typedef int w_type;
    struct node
    {
        int v;
        w_type w;
        node(){}
        node(int _v,w_type _w):v(_v),w(_w){}
        bool operator<(node x) const {return w<x.w;}
        bool operator>(node x) const {return w>x.w;}
    };
    struct edge
    {
        int u,v;
        w_type w;
        edge(){}
        edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
        bool operator<(edge x) const {return w<x.w;}
        bool operator>(edge x) const {return w>x.w;}
    };
    vector<node> g[_mxn];
    vector<edge> e;
    void add(int u,int v,w_type w)
    {
        g[u].push_back(node(v,w));
        e.push_back(edge(u,v,w));
    }
}g;
struct dsu
{
    int f[_mxn],siz[_mxn];
    void init(int n)
    {
        for(int i=1;i<=n;i++)
            f[i]=i,siz[i]=1;
    }
    int find(int x)
    {
        if(f[x]==x)
            return x;
        return find(f[x]);
    }
    void merge(int x,int y)
    {
        int fx=find(x),fy=find(y);
        if(fx!=fy)
        {
            f[fy]=fx;
            siz[fx]+=siz[fy];
        }
    }
    bool same(int x,int y){return find(x)==find(y);}
    int size(int x){return siz[find(x)];}
};
int kruskal(int n)
{
    dsu t;
    t.init(n);
    sort(g.e.begin(),g.e.end());
    int res=0;
    int cnt=0;
    for(auto it:g.e)
    {
        if(!t.same(it.u,it.v))
        {
            t.merge(it.u,it.v);
            res+=it.w;
            cnt++;
            if(cnt==n-1)
                break;
        }
    }
    if(cnt<n-1)
        return -1;
    return res;
}
int main()
{
    ___();
    cin>>n;
    int sum=0;
    for(int i=1;i<=n;i++)
        cin>>a[i],sum+=a[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==j)
                continue;
            g.add(i,j,lcm(a[i],a[j]));
        }
    }
    cout<<sum+kruskal(n)<<endl;
    return 0;
}

C

题意

给定一棵边带权的树,询问 \(m\) 次,每次两个人从两个节点向 \(1\) 号节点走,走一条边花费边权分钟,先走到两人共同路径的起点的人会开始等待直到另一个人到,求等待时间。

题解

“两人共同路径的起点”显然是 LCA。设两个人分别为 \(u,v\)\(\operatorname{dis}(u)\)\(u\) 到根节点距离,则 \(u\to \operatorname{LCA}\) 的距离即为 \(\operatorname{dis}(u)-\operatorname{dis}(\operatorname{LCA})\)\(v\) 同理,答案即为 \(|(\operatorname{dis}(u)-\operatorname{dis}(\operatorname{LCA}))-(\operatorname{dis}(v)-\operatorname{dis}(\operatorname{LCA}))|=|\operatorname{dis}(u)-\operatorname{dis}(\operatorname{LCA})-\operatorname{dis}(v)+\operatorname{dis}(\operatorname{LCA})|=|\operatorname{dis}(u)-\operatorname{dis}(v)|\),我们惊奇的发现 \(\operatorname{dis}(\operatorname{LCA})\) 被消掉了,所以我们连 LCA 都不用求,直接预处理 \(\operatorname{dis}(u)\) 即可。

代码:

#define ll long long
const int _mxn=100000+5;
struct graph
{
    typedef int w_type;
    struct node
    {
        int v;
        w_type w;
        node(){}
        node(int _v,w_type _w):v(_v),w(_w){}
        bool operator<(node x) const {return w<x.w;}
        bool operator>(node x) const {return w>x.w;}
    };
    struct edge
    {
        int u,v;
        w_type w;
        edge(){}
        edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
        bool operator<(edge x) const {return w<x.w;}
        bool operator>(edge x) const {return w>x.w;}
    };
    vector<node> g[_mxn];
    vector<edge> e;
    void add(int u,int v,w_type w)
    {
        g[u].push_back(node(v,w));
        e.push_back(edge(u,v,w));
    }
}g;
int n;
ll dis[_mxn];
void dfs(int u,int fa)//预处理 dis
{
    for(auto it:g.g[u])
    {
        if(it.v==fa)
            continue;
        dis[it.v]=dis[u]+it.w;
        dfs(it.v,u);
    }
}
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        g.add(u,v,w),g.add(v,u,w);
    }
    dfs(1,0);
    int m;
    cin>>m;
    while(m--)
    {
        int u,v;
        cin>>u>>v;
        cout<<abs(dis[u]-dis[v])<<endl;
    }
    return 0;
}

D

题意

给定长为 \(n\) 的序列 \(a\) 和一个非负整数 \(x\),询问 \(m\) 次,每次询问 \(a\)\([l,r]\) 区间是否存在两个数异或等于 \(x\)

题解

首先要使 \(a_i\oplus a_j=x\),那么 \(a_j=a_i\oplus x\)。我们可以对于每个 \(i\) 存储需要的 \(a_j\) 的最后出现位置,记作 \(s_i\)。询问时对 \(s\) 求区间最大值,如果这个值在区间内就有,否则没有。

代码:

const int _mxn=1e5+5;
int n,a[_mxn],lst[_mxn],t[1<<20];
struct ST
{
    int fmx[_mxn][30],fmn[_mxn][30];
    void initmax(int *a)
    {
        for(int i=1;i<=n;i++)
            fmx[i][0]=a[i];
        int t=log2(n);
        for(int j=1;j<=t;j++)
            for(int i=1;i<=n-(1<<j)+1;i++)
                fmx[i][j]=max(fmx[i][j-1],fmx[i+(1<<(j-1))][j-1]);
    }
    void initmin(int *a)
    {
        for(int i=1;i<=n;i++)
            fmn[i][0]=a[i];
        int t=log2(n);
        for(int j=1;j<=t;j++)
            for(int i=1;i<=n-(1<<j)+1;i++)
                fmn[i][j]=min(fmn[i][j-1],fmn[i+(1<<(j-1))][j-1]);
    }
    void init(int *a)
    {
        initmax(a);
        initmin(a);
    }
    int qmax(int l,int r)
    {
        int t=log2(r-l+1);
        return max(fmx[l][t],fmx[r-(1<<t)+1][t]);
    }
    int qmin(int l,int r)
    {
        int t=log2(r-l+1);
        return min(fmn[l][t],fmn[r-(1<<t)+1][t]);
    }
}st;
int m,x;
int main()
{
    ___();
    cin>>n>>m>>x;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        int y=a[i]^x;
        if(t[y])
            lst[i]=t[y];
        else
            lst[i]=-1;
        t[a[i]]=i;
    }
    st.init(lst);
    while(m--)
    {
        int l,r;
        cin>>l>>r;
        cout<<(st.qmax(l,r)>=l?"yes":"no")<<endl;
    }
    return 0;
}

E

题意

[NOIP 2016 提高组] 天天爱跑步,洛谷 P1600

题解(来自老师)



代码:

#define ll long long
const int _mxn=3e5+5;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int n,m,w[_mxn],ans[_mxn];
namespace LCA
{
    int deep[_mxn],f[_mxn][30],lg;
    void init(int u,int fa,int dep)
    {
        deep[u]=dep;
        f[u][0]=fa;
        for(int i=1;i<=lg;i++)
            f[u][i]=f[f[u][i-1]][i-1];
        for(auto it:g[u])
        {
            if(it==fa)
                continue;
            init(it,u,dep+1);
        }
    }
    int query(int x,int y)
    {
        if(deep[x]<deep[y])
            swap(x,y);
        for(int i=lg;i>=0;i--)
            if(deep[f[x][i]]>=deep[y])
                x=f[x][i];
        if(x==y)
            return x;
        for(int i=lg;i>=0;i--)
            if(f[x][i]!=f[y][i])
                x=f[x][i],y=f[y][i];
        return f[x][0];
    }
}
using LCA::f;
using LCA::deep;
vector<int> ins1[_mxn],ins2[_mxn],del1[_mxn],del2[_mxn];
map<int,int> mp1,mp2;
void dfs(int u,int fa)
{
    ans[u]-=mp1[w[u]+deep[u]]+mp2[w[u]-deep[u]];
    for(int it:ins1[u])
        mp1[it]++;
    for(int it:del1[u])
        mp1[it]--;
    for(int it:ins2[u])
        mp2[it]++;
    for(int it:del2[u])
        mp2[it]--;
    for(int it:g[u])
        if(it!=fa)
            dfs(it,u);
    ans[u]+=mp1[w[u]+deep[u]]+mp2[w[u]-deep[u]];
}
int main()
{
    ___();
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v),add(v,u);
    }
    LCA::lg=log2(n),LCA::init(1,0,1);
    for(int i=1;i<=n;i++)
        cin>>w[i];
    while(m--)
    {
        int u,v;
        cin>>u>>v;
        int lca=LCA::query(u,v);
        if(deep[u]>=deep[lca])
        {
            ins1[u].push_back(deep[u]);
            del1[f[lca][0]].push_back(deep[u]);
        }
        if(deep[v]>deep[lca])
        {
            ins2[v].push_back(deep[u]-2*deep[lca]);
            del2[lca].push_back(deep[u]-2*deep[lca]);
        }
    }
    dfs(1,0);
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<" ";
    cout<<endl;
    return 0;
}

Day 7

Dijkstra

int dis[_mxn];
void dijkstra(int s)
{
    memset(dis,0x3f,sizeof(dis));
    typedef graph::node node;
    priority_queue<node,vector<node>,greater<node> > q;
    dis[s]=0;
    q.push(node(s,0));
    while(!q.empty())
    {
        node nw=q.top();q.pop();
        int u=nw.v;
        if(nw.w>dis[u])
            continue;
        for(auto it:g.g[u])
        {
            if(dis[u]+it.w<dis[it.v])
            {
                dis[it.v]=dis[u]+it.w;
                q.push(node(it.v,dis[it.v]));
            }
        }
    }
}

SPFA

int dis[_mxn],cnt[_mxn];
bool in[_mxn];
bool spfa(int s)
{
    queue<int> q;
    q.push(s),dis[s]=0,in[s]=true,cnt[s]=1;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        in[u]=false;
        for(auto it:g.g[u])
        {
            if(dis[u]+it.w<dis[it.v])
            {
                dis[it.v]=dis[nw]+w;
                if(!in[it.v])
                {
                    q.push(it.v),in[it.v]=true;
                    if(++cnt[it.v]>n)
                        return false;
                }
            }
        }
    }
    return true;
}

Floyd

太简单了懒得贴了。

拓扑排序

  1. 遍历所有入度为 \(0\) 的点,加入队列;
  2. 取出队头,遍历队头指向的点,这些点入度 \(-1\),减完入度为 \(0\) 就入队并输出;
  3. 重复 2 直到队列为空。

代码:

int deg[_mxn],tdeg[_mxn];
bool toposort()
{
    queue<int> q;
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        if((tdeg[i]=deg[i])==0)
        {
            q.push(i);
            cnt++;
            cout<<i<<" ";
        }
    }
    int lv=0;
    while(!q.empty())
    {
        int siz=q.size();
        while(siz--)
        {
            int u=q.front();q.pop();
            for(int it:g[u])
            {
                tdeg[it]--;
                if(tdeg[it]==0)
                {
                    q.push(it);
                    cnt++;
                    cout<<it<<" ";
                }
            }
        }
        lv++;
    }
    return cnt==n;
}

Day 7 模拟赛

A

题意

给定一个不超过 \(5\) 位的正整数 \(n\) 和一位正整数 \(m\),可以把 \(m\) 插入 \(n\) 的任意位置,包括开头结尾,求结果最大值。

题解

签到题。存成 string,枚举插入位置,插完的串为 n.substr(0,i)+m+n.substr(i,l-i),转成 int 取最大就行了。太简单了代码懒得放了。

B

题意

给定 \(n(2\le n\le10)\),求 \(1\sim n\) 的所有排列中,相邻两数之和均不为素数的排列个数。

题解

dfs 全排列然后直接判断就行了。太简单了代码懒得放了。

C

题意

给定三个正整数 \(n,m,b\) 和一张 \(n\) 个点 \(m\) 条边的边带权无向图,点有点权,求 \(1\sim n\) 的路径中 边权和 \(\le b\) 的路径上点权的最大值 的最小值。

洛谷 P1462

题解

给点权数组排个序,然后二分,check 里跑个 dijkstra,当前点点权如果 \(>mid\) 就不走,最后判断到 \(n\) 的最短路是否 \(\le b\) 即可。

代码:

#define ll long long
const int _mxn=1e4+5;
struct graph
{
    typedef int w_type;
    struct node
    {
        int v;
        w_type w;
        node(){}
        node(int _v,w_type _w):v(_v),w(_w){}
        bool operator<(node x) const {return w<x.w;}
        bool operator>(node x) const {return w>x.w;}
    };
    struct edge
    {
        int u,v;
        w_type w;
        edge(){}
        edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
        bool operator<(edge x) const {return w<x.w;}
        bool operator>(edge x) const {return w>x.w;}
    };
    vector<node> g[_mxn];
    vector<edge> e;
    void add(int u,int v,w_type w)
    {
        g[u].push_back(node(v,w));
        e.push_back(edge(u,v,w));
    }
}g;
int n,m,b,f[_mxn],t[_mxn];
int dis[_mxn];
bool check(int mxf)//dijkstra 的 check
{
    typedef graph::node node;
    priority_queue<node,vector<node>,greater<node> > q;
    memset(dis,0x3f,sizeof(dis));
    dis[1]=0;
    q.push(node(1,0));
    while(!q.empty())
    {
        node nw=q.top();q.pop();
        int u=nw.v;
        if(nw.w>dis[u]||f[u]>mxf)
            continue;
        for(auto it:g.g[u])
        {
            if(f[it.v]>mxf)
                continue;
            if(dis[u]+it.w<dis[it.v])
            {
                dis[it.v]=dis[u]+it.w;
                q.push(node(it.v,dis[it.v]));
            }
        }
    }
    return dis[n]<=b;
}
int main()
{
    ___();
    cin>>n>>m>>b;
    for(int i=1;i<=n;i++)
        cin>>f[i],t[i]=f[i];
    sort(t+1,t+n+1);
    while(m--)
    {
        int u,v,w;
        cin>>u>>v>>w;
        g.add(u,v,w),g.add(v,u,w);
    }
    int l=1,r=n+1;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(check(t[mid]))
            r=mid;
        else
            l=mid+1;
    }
    if(r>n)
        cout<<"AFK"<<endl;
    else
        cout<<t[r]<<endl;
    return 0;
}

D

题意

题解

老师的 std:

#include<bits/stdc++.h>
using namespace std;
#define MAX 1005
vector<int> e[MAX],E[MAX];
int deg[MAX];
int n,m,D,tot;
int ary[MAX],bg[MAX]; //拓扑序;起始节点
bool flg[MAX],vis[MAX];//一定发生,可能未发生,
bool ban[MAX],hped[MAX];
void ck(int id){ //判断id是否一定发生
    memset(ban,0,sizeof(ban));
    memset(hped,0,sizeof(hped));
    ban[id]=1;
    for(int i=n;i>=1;i--){
        int x=ary[i];
        if(ban[x]) for(auto y:E[x]) ban[y]=1;
    }
    // for(int i=1;i<=n;i++) cout<<ban[i]<<" ";
    // cout<<endl;
    for(int i=1;i<=n;i++){
        int x=ary[i];
        if(ban[x]) continue;
        if(bg[x]) hped[x]=1;
        if(hped[x])
            for(auto y:e[x])
                hped[y]=1;
    }
    for(int i=1;i<=n;i++){
        int x=ary[i];
        if(flg[x]){
            if(ban[x]||!hped[x]){
                flg[id]=1; return;
            }
        }
    }
    vis[id]=1;
    for(int i=1;i<=n;i++)
        if(ban[i]) vis[i]=1;
}
void sol(){
    cin>>n>>m>>D;
    for(int i=1,x,y;i<=m;i++){
        cin>>x>>y;
        e[x].push_back(y);
        E[y].push_back(x);
        deg[y]++;
    }
    for(int i=1,x;i<=D;i++){
        cin>>x;flg[x]=1;
    }
    queue<int> q;
    for(int i=1;i<=n;i++){
        if(!deg[i]){
            q.push(i);
            bg[i]=1;
        }
    }
    while(!q.empty()){
        int x=q.front();q.pop();
        ary[++tot]=x;
        for(auto y:e[x]){
            deg[y]--;
            if(!deg[y]) q.push(y);
        }
    }
    for(int i=1;i<=n;i++) if(!vis[i]){
        if(!flg[i]) ck(i);
        if(flg[i]) cout<<i<<" ";
    }
    //for(int i=1;i<=n;i++) cout<<flg[i]<<endl;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int t=1;//cin>>t;
	while(t--) sol();
} 

E

题意

CF1205B

题解

显然 \(0\) 对答案没有贡献,所以先直接去掉。最小环最小显然只能是 \(3\),所以只要所有的数里有一个二进制位有三个 \(1\) 那么按位与后就会有至少三组数的与不为 \(0\)。数据范围一共 \(10^{18}\),也就是大约 \(2^{64}\)(虽然差了不少),那么根据抽屉原理,只要有大于 \(2\times64\) 个非零数,那么就会有至少一个二进制位有至少 \(3\)\(1\),所以 \(n>128\) 时答案一直为 \(3\)。这样数据范围就被缩小到了 \(n\le128\)\(O(n^3)\) 可解,所以直接打个 Floyd 的最小环板子(洛谷 P6175)就行了。

代码:

#define ll long long
const int _mxn=1000+5;
int n;
vector<ll> a;
ll g[_mxn][_mxn],dis[_mxn][_mxn];//因为神秘原因,不开 long long 会 95 分 WA
ll ans=1e18;
int main()
{
    ___();
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        ll x;
        cin>>x;
        if(x)//去掉 0
            a.push_back(x);
    }
    n=a.size();//更新 n
    if(n<=128)//打个 Floyd 最小环板子
    {
        memset(g,0x1f,sizeof(g));
        memset(dis,0x1f,sizeof(dis));
        for(int i=1;i<=n;i++)
            g[i][i]=dis[i][i]=0;
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
                if(a[i-1]&a[j-1])
                    g[i][j]=g[j][i]=dis[i][j]=dis[j][i]=1;
        for(int k=1;k<=n;k++)
        {
            for(int i=1;i<k;i++)
                for(int j=i+1;j<k;j++)
                    ans=min(ans,dis[i][j]+g[i][k]+g[k][j]);
            for(int i=1;i<=n;i++)
                for(int j=1;j<=n;j++)
                    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
        }
        cout<<(ans==1e18?-1:ans)<<endl;
    }
    else
        cout<<3<<endl;
    return 0;
}
/*
1&1=1
1&0=0
0&0=0
*/

Day 8

线性 DP 与区间 DP

Day 8 模拟赛

A

题意

给定一个长为 \(n\) 的序列 \(a\),把序列里每个数依次加入另一个序列的前面,如果被加入的序列含有加入的数则把该数从原位置调到序列开头。求最后被加入到序列。

题解

签到题。倒序遍历原序列,如果当前值未被标记则输出并标记。代码懒得放了。

B

题意

\(n\) 个任务,每天可以完成 \(1\)\(2\) 个,求完成所有任务的方案数。

题解

\(f_i\) 为完成 \(i\) 个任务的方案数,那么有转移方程 \(f_i=f_{i-1}+f_{i-2}\),发现就是斐波那契数列。直接算就行了,代码懒得放了。

C

题意

给定一个长 \(n\) 的整数序列 \(a\),在其中选取恰好 \(k\) 个无重叠的非空连续子序列,第 \(i\) 个子序列价值为 \(s_i\times i\)(其中 \(s_i\) 为),求这些子序列的总价值最大值。

题解

类似去年 Day 7 B。

\(f_{i,j}\) 表示第 \(i\) 段结尾为 \(j\) 的最大收益,那么有两种情况:

  • 不开新段,\(f_{i,j}=f_{i,j-1}+a_j*i\)
  • 开新段,\(f_{i,j}=\max_{k=1}^{j-1}f_{i-1,k}\)

转移取最大即可。但是如果每次转移都要求一遍 \(\max_{k=1}^{j-1}f_{i-1,k}\) 的话时间复杂度是 \(O(kn^2)\) 的,会超时。考虑定义 \(x_{i,j}=\max_{k=1}^{j}f_{i,k}\) 这个可以在转移的时候同时求,最终答案就是 \(x_{k,n}\)

代码:

#define ll long long
const int _mxn=1e5+5,_mxk=1000+5;
int n,k,a[_mxn];
ll f[_mxk][_mxn],mx[_mxk][_mxn];//f[i][j]:第 i 段结尾为 j 的最大收益;mx[i][j]:f[i][1~j] 的最大值
int main()
{
    ___();
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n>>k;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        for(int i=1;i<=k;i++)
        {
            for(int j=0;j<=n;j++)
                f[i][j]=mx[i][j]=-1e12;
        }
        for(int i=1;i<=k;i++)
        {
            for(int j=1;j<=n;j++)
            {
                f[i][j]=max(f[i][j-1],mx[i-1][j-1])+a[j]*i;
                mx[i][j]=max(mx[i][j-1],f[i][j]);
            }
        }
        cout<<mx[k][n]<<endl;
    }
    return 0;
}

D

题意

CF1025D

题解

显然给出的序列是需要构造的二叉搜索树的中序遍历。那么我们枚举根节点然后判断左右半边行不行就好了。但是直接搜是会 TLE 的,那么我们加个记忆化,定义 \(f_{l,r,x}\) 表示 \([l,r]\) 是否可行,\(x=0\) 表示根为 \(l-1\),否则为 \(r+1\)

代码:

const int _mxn=700+5;
int n,a[_mxn],gcd[_mxn][_mxn];
bool f[_mxn][_mxn][2],vis[_mxn][_mxn][2];//f[l][r][x] 表示 [l,r] 是否可行,x=0 表示根为 l-1,否则为 r+1
bool dfs(int u,int l,int r,int d)//u 为根,[l,r] 区间能否组成二叉搜索树
{
    if(l>r)
        return true;
    if(vis[l][r][d])
        return f[l][r][d];
    vis[l][r][d]=true;
    for(int i=l;i<=r;i++)
    {
        if(gcd[u][i]>1)
        {
            if(dfs(i,l,i-1,0)&&dfs(i,i+1,r,1))
                return f[l][r][d]=true;
        }
    }
    return f[l][r][d]=false;
}
int main()
{
    ___();
    int _;
    cin>>_;
    while(_--)
    {
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            for(int j=1;j<i;j++)
                gcd[i][j]=gcd[j][i]=__gcd(a[i],a[j]);
        }
        memset(f,false,sizeof(f));
        memset(vis,false,sizeof(vis));
        bool fans=false;
        for(int i=1;i<=n;i++)
        {
            if(dfs(i,1,i-1,0)&&dfs(i,i+1,n,1))
            {
                fans=true;
                break;
            }
        }
        cout<<(fans?"Yes":"No")<<endl;
    }
    return 0;
}

E

题意

CF762D

题解

老师的 std:

#include<bits/stdc++.h>
using namespace std;
#define MAX 100005
#define ll long long
int n;
ll a[MAX][3];
ll f[MAX][3];
ll val(int i,int p,int q){
    if(q<p) swap(p,q);
    ll ret=0;
    for(int o=p;o<=q;o++) ret+=a[i][o];
    return ret; 
}
ll qz[MAX];
void sol(){
	cin>>n;
    for(int j=0;j<3;j++)
        for(int i=1;i<=n;i++)
            cin>>a[i][j];
    for(int i=1;i<=n;i++)
        qz[i]=qz[i-1]+val(i,0,2);
    memset(f,0x80,sizeof(f));
    f[0][0]=0;
    ll mx0=0,mx2=-1e18;
    for(int i=1;i<=n;i++){
        for(int p=0;p<3;p++)
            for(int q=0;q<3;q++){
                f[i][p]=max(f[i][p],
                f[i-1][q]+val(i,p,q));
            }
        f[i][2]=max(f[i][2],mx0+qz[i]);
        f[i][0]=max(f[i][0],mx2+qz[i]);
        mx0=max(mx0,f[i][0]-qz[i]);
        mx2=max(mx2,f[i][2]-qz[i]);
    }
    cout<<f[n][2]<<endl;
} 
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t=1;cin>>t;
    while(t--) sol();
    return 0;
}

Day 9

状压 DP

Day 9 模拟赛

A

题意

给定长为 \(n(1\le n\le1000)\) 的序列 \(a\),求序列中任意两个数差值的绝对值的最小值。

题解

\(O(n^2)\) 暴力枚举,代码不放了。

B

题意

考古学家小可发现了一座神秘法老陵墓,墓中有 \(m\) 个储藏室。每个储藏室 \(i\) 包含 \(a_i\) 个黄金宝箱,每个宝箱内装有 \(b_i\) 枚法老金币。但是因为人手不足,小可的考古队只能携带 \(n\) 个宝箱离开古墓。宝箱不可拆分,只能整箱取走。求最多能获得的金币数。

题解

直接取最大价值的 \(n\) 个宝箱就行了。代码不放了。

C

D

E

posted @ 2025-07-30 19:57  电乔  阅读(50)  评论(1)    收藏  举报