26牛客多校9

B

有一个n×n网格,其中m个不同的格子已经点亮。可以继续点亮任意未亮格子。
求至少还要点亮多少个格子,才能出现一整行或一整列全部点亮。

简单计数问题,取择亮格最多的一行或一列即可



H

给定集合 S 含 n 个不同的正整数。每次选择 x ∈ S ,拆成正整数 u + v = x ,再依次对 x , u , v 做集合对称差(若 S 中已有该元素,删去,否则将该元素加入集合)。
问经过任意次操作后,可以得到多少种只含一个元素的集合。

显然有 u + v +x = 2 * x ,

  • x 与 u , v 的奇偶关系只取决于 x;
  • 经过异或操作后,可以不断改变集合中的元素,但最终只剩一个数时,其奇偶性由初始集合中奇数元素个数的奇偶性决定。

设:

  • o:初始集合中奇数的个数;
  • e:初始集合中偶数的个数;
  • mo:最大的奇数;
  • me:最大的偶数。

当 o 为偶数时,最终单元素的奇偶性为偶数,答案取 me / 2​和(mo − 1)/ 2​ 较大值

当 o 为奇数时,最终单元素的奇偶性为奇数,答案取 me / 2​和(mo + 1)/ 2 较大值​



I

敌人有x点生命;每回合选择增加a层中毒,或立即造成b点普通伤害。行动后结算一次中毒:造成当前层数的伤害,再令层数减一。
求最少多少回合能击杀敌人。

枚举前多少回合使用毒,再用二分寻找最少回合数,中毒伤害形成一个先递增、后递减的等差数列。



F

在 n × n 网格中,第 i 行第 pi 列初始亮灯,其中p是一个长度为 n 的排列。
每秒,所有至少有两个相邻亮灯的未亮格子同时亮起,且不会熄灭。
判断是否最终全亮;若可以,求全亮的最早时刻。

观察得以下关键性质:

  1. 亮起的区域始终可以看成若干个完整正方形   -->  可以把整个过程从  模拟 n2 个格子 变成 维护若干个块
  2. 两个块能否合并,可以只看值域   -->  可以使用栈,每个块只会入栈、出栈有限次

块维护以下信息:

  • n:边长;
  • l , r:这个块的值域;
  • a:左上角最早亮起时间;
  • b:右上角最早亮起时间;
  • c:左下角最早亮起时间;
  • d:右下角最早亮起时间;
  • ans:整个块全部亮起的时间

又由于两个完整块合并后,中间新增出来的区域,本质上都是从某些边界向内部逐层扩张。一个格子的最早时间只取决于它能够从哪一侧开始获得两个已经亮起的邻居,因此整个区域的最慢位置一定可以由四个角的状态递推出来,则块只需要维护四个角的信息即可

如果最终能够让整个网格全部亮起,那么所有初始的 n 个亮点必须最终合并成一个完整的块,否则说明至少存在一部分区域永远无法满足“至少两个相邻亮灯”的条件,答案为 -1 。

F.Light the Lamp

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define fep(i,j,k) for(int i=j;i<=k;i++)
#define fepp(i,j,k) for(int i=j;i<k;i++)
#define fepd(i,j,k,l) for(int i=j;i<=k;i+=l)
#define feppd(i,j,k,l) for(int i=j;i<k;i+=l)
#define fed(i,j,k) for(int i=j;i>=k;i--)
#define fedd(i,j,k) for(int i=j;i>k;i--)
#define fedp(i,j,k,l) for(int i=j;i>=k;i-=l)a
#define feddp(i,j,k,l) for(int i=j;i>k;i-=l)
using namespace std;
const int N=5e5+5;
struct L
{
    int n,a,b,c,d,ans,l,r;
};
void solve()
{
    int n;
    cin>>n;
    vector<L> light;
    light.reserve(n);
    fep(i,1,n)
    {
        int x;
        cin>>x;
        light.push_back({1,0,0,0,0,0,x,x});
        while(light.size()>=2)
        {
            L y=light.back();
            L x=light[light.size()-2];
            int l=min(x.l,y.l),r=max(x.r,y.r);
            if(r-l+1!=x.n+y.n) break;
            light.pop_back();
            light.pop_back();
            int u=x.n,v=y.n;
            L z;
            z.n=u+v;
            z.l=l;
            z.r=r;
            if(x.l<y.l)
            {
                z.a=x.a;
                z.b=max({x.b+v,x.d+u+v-1,y.a+u+v-1,y.b+u});
                z.c=max({x.c+v,x.d+u+v-1,y.a+u+v-1,y.c+u});
                z.d=y.d;
            }
            else
            {
                z.a=max({x.a+v,x.c+u+v-1,y.b+u+v-1,y.a+u});
                z.b=x.b;
                z.c=y.c;
                z.d=max({x.d+v,x.c+u+v-1,y.b+u+v-1,y.d+u});
            }
            z.ans=max({x.ans,y.ans,z.a,z.b,z.c,z.d});
            light.push_back(z);
        }
    }
    if(light.size()!=1) cout<<-1<<endl;
    else cout<<light[0].ans<<endl;
    return; 
}
signed main()
{
    IOS
    solve();
    return 0;
}


L

给定长度为1018的初始全零的01序列,给定2n个端点,你可以将它们任意两两配对为( xi , yi ),其中 xi ≤ yi,然后将序列上每个闭区间[ xi , yi ]整段异或1,求最终序列中1的个数的最小值

本题标准题解给了个四模状态的DP做法,个人感觉非常繁琐,这里给出一种代码非常简单的结论做法

概要:区间异或的最终状态只由覆盖次数的奇偶性决定,所以可以通过排序端点后的相邻配对,把问题压缩成区间长度之和,再处理重叠带来的奇偶修正。

先贴代码:

L.XOR Team

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define fep(i,j,k)  for(int i=j;i<=k;i++)
#define fepp(i,j,k) for(int i=j;i<k;i++)
#define fepd(i,j,k,l)  for(int i=j;i<=k;i+=l)
#define feppd(i,j,k,l)  for(int i=j;i<k;i+=l)
#define fed(i,j,k)  for(int i=j;i>=k;i--)
#define fedd(i,j,k) for(int i=j;i>k;i--)
#define fedp(i,j,k,l)  for(int i=j;i>=k;i-=l)
#define feddp(i,j,k,l)  for(int i=j;i>k;i-=l)
using namespace std;
const int N=2e6+5;
int a[N];
void solve()
{
    int n;
    cin>>n;
    fepp(i,0,2*n) cin>>a[i];
    sort(a,a+2*n);
    int ans=0;
    fepp(i,0,n) ans+=a[2*i+1]-a[2*i];
    int k=0;
    fep(i,1,n-2)if(a[2*i]!=a[2*i+1]) k++;
    ans+=((n-k)%2)-k;
    cout<<ans<<endl;
    return;
}
signed main()
{
    IOS
    int t;
    cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}

一次操作会让区间内所有位置异或 1。最终某个位置是否为 1,只取决于覆盖它的区间数量的奇偶性。

为了使最终 1 的总数尽可能少,首先将所有端点排序,则​最基础的配对方式是将相邻元素配成:(a1​,a2​),(a3​,a4​),…,(a2n−1​,a2n​),显然这些区间的总长度贡献为所有数对做差的累加和。

但是不同区间之间可能产生重叠,重叠部分会被抵消。因此还需要考虑相邻两对之间,[a2i−1​,a2i​]和[a2i+1​,a2i+2​],如果中间存在有效重叠,即:a2i​ < a2i+1​,则会影响最终奇偶覆盖关系。

最后根据这些位置的奇偶性进行整体修正,也就是在基础区间长度和的基础上,根据中间不同端点个数的奇偶性进行。

因此整个问题可以转化为:

  1. 将所有 2n 个端点排序;
  2. 相邻端点优先配对;
  3. 基础代价为所有相邻差之和;
  4. 根据相邻区间之间是否存在间隔,对答案进行一次奇偶修正。


C

数轴上有n个点,同一位置可以有多个点。
一次操作选择含至少两个点的位置x,移走两个点,并在x−1,x+1各放一个点。
可执行任意多次操作,求最终位置集合的最小可能mex。

(此人半天看不懂题解)

  • 阿贝尔性质

    本题操作等价于一维数轴上的 Chip-firing Game。操作具有阿贝尔性质(顺序无关性):无论以何种顺序进行合法的推点操作,最终能达到的状态集与极小清空状态是唯一确定的。

  • MEX 最小值的等价转化

    要求最小的 mex,等价于寻找最小的非负整数 x,使得存在某种合法操作序列能够将位置 x 处的点数彻底清空为 0。因为从 x=0 开始从左到右贪心枚举,第一个能够被清空的位置即为答案。

  • 奇偶性与双向奔赴双向传递性

    对于位置 x,假设其当前聚集的初始点数为 cx:

    • 偶数情况:点数为偶数时具有自清空能力。所有点可两两成对向两边推开,自身保留 0 个点,必定能被清空。

    • 奇数情况:点数为奇数时,内部自推 k = cx/ 2 向下取整 次后必定残留 1 个点。要清空这最后 1 个点,必须依靠左侧或右侧提供至少 1 个点(即凑够 2 个点发起最后一次推离)。

  • 界限与级联传输(抽屉原理)

    • 答案上界:由于总点数为 n,根据抽屉原理,最终 mex 绝不会超过 n+1。

    • 右侧影响范围:由于“多米诺效应”(连续位置各留 1 个点构成的骨牌),右侧极远处的点可能通过骨牌效应一路向左传递。

Drifting Toward a Promised Sky 2(调shi我了)

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define fep(i,j,k) for(int i=j;i<=k;i++)
#define fepp(i,j,k) for(int i=j;i<k;i++)
#define fepd(i,j,k,l) for(int i=j;i<=k;i+=l)
#define feppd(i,j,k,l) for(int i=j;i<k;i+=l)
#define fed(i,j,k) for(int i=j;i>=k;i--)
#define fedd(i,j,k) for(int i=j;i>k;i--)
#define fedp(i,j,k,l) for(int i=j;i>=k;i-=l)
#define feddp(i,j,k,l) for(int i=j;i>k;i-=l)
using namespace std;
const int N=1e5+5;
void solve()
{
    int n;
    cin>>n;
    vector<int>a(n),c(n+2);
    int s=0;
    fep(i,0,n-1)
    {
        cin>>a[i];
        s+=a[i];
        if(a[i]<=n)c[a[i]]++;
    }
    int m=0;
    while(c[m])m++;
    if(m==0)
    {
        cout<<0<<endl;
        return;
    }
    int w=c[0];
    if(w%2==0)
    {
        cout<<0<<endl;
        return;
    }
    int q=(w-1)/2;
    int r=n-q;
    int k;
    if(r==1)k=s+q;
    else k=s+q-(r-2)*(r-1)/2;
    if(r>=2&&k<r-1)
    {
        cout<<0<<endl;
        return;
    }
    sort(a.rbegin(),a.rend());
    int x=0,y=0;
    fep(i,0,n-1)
    {
        x+=a[i];
        if(i==0)y+=k;
        else if(i<r)y+=r-1-i;
        else y--;
        if(y<x)
        {
            cout<<0<<endl;
            return;
        }
    }
    cout<<m<<endl;
    return;
}
signed main()
{
    IOS
    int t;
    cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}


D

m 个人分别在时刻si 出现在树上结点xi,随后以单位速度沿最短路走向根1。任意时刻若至少两人在同一位置,则这些人同时消失;位置也可以在边内部。
未发生碰撞并到达根的人成功逃脱,输出每个人的结果。

liyuu太强大了喵

本质上是一个在树上合并信息的过程

D.Escape Root

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
// #define endl '\n'
#define lr(i, a, b) for(int i = a; i <= b; i++)
#define rl(i,b,a) for(int i=b;i>=a;i--)
const int maxn=1e6+10;
const int inf=1e9+10;
vector<int> u[maxn];
vector<int> c[maxn];
int cnt;
int dep[maxn];
int dnf[maxn];
int id[maxn];
int lg[maxn];
int f[maxn][20];
int ans[maxn];
pair<int,int> q[maxn];
void buildst()
{
    cnt--;
	for(int i=1;i<=cnt;i++) f[i][0]=dnf[i];
    for(int j=1;j<=20;j++)
    {
        for(int i=1;i+(1<<j)<=cnt;++i){
			int f1=f[i][j-1];
            int f2=f[i+(1<<(j-1))][j-1];
			f[i][j]=dep[f1]<dep[f2]?f1:f2;
		}
    }
    lg[0]=-1;
    for(int i=1;i<maxn;i++) lg[i]=lg[i>>1]+1;
}
inline int getlca(int u,int v)
{
	if(id[u]>id[v])swap(u,v);
	u=id[u],v=id[v];
	int kk=lg[v-u+1],f1=f[u][kk],f2=f[v-(1<<kk)+1][kk];
	return dep[f1]<dep[f2]?f1:f2;
}
inline void dfs(int x,int f)
{
    id[x]=cnt;
    dnf[cnt]=x;
    cnt++;
    dep[x]=dep[f]+1;
    for(auto v:u[x])
    {
        if(f==v) continue; 
        dfs(v,x);
        dnf[cnt]=x;
        cnt++;
    }
}
unordered_map<int,vector<int>> ma;
vector<int> shi[maxn];
inline void dfs1(int x,int f)
{
    for(auto now:c[x])
    {
        auto [id,t] = q[now];
        if(ma[t+dep[x]].size()!=0)
        {
            // cout<<getlca(q[ma[t+dep[x]].back()].first,x)<<endl;
            shi[getlca(q[ma[t+dep[x]].back()].first,x)].push_back(t+dep[x]);
        }
        ma[t+dep[x]].push_back(now);
        // cout<<x<<' '<<now<<endl;
    }
    for(auto v:u[x])
    {
        if(f==v) continue; 
        dfs1(v,x);
    }
    for(int now:shi[x])
    {
        // cout<<now<<' '<<x<<endl;
        if(ma[now].size()>=2)
        {
            int p=0;
            while(ma[now].size()>=2)
            {
                int t=ma[now].size();
                if(getlca(q[ma[now][t-1]].first,q[ma[now][t-2]].first)==x)
                {
                    p=1;
                    ma[now].pop_back();
                }
                else
                {
                    break;
                }
            }
            if(p==1) ma[now].pop_back();
        }
    }
    // for(auto [xx,y] : ma)
    // {
    //     for(auto now:y)
    //     {
    //         cout<<xx<<' '<<now<<' '<<x<<endl;
    //     }
    // }
}
void solve()
{
    // map<int,vector<int>> ma;
    cnt=1;
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) u[i].clear();
    for(int i=1;i<=n-1;i++)
    {
        int x,y;
        cin>>x>>y;
        u[x].push_back(y);
        u[y].push_back(x);
    }
    dfs(1,1);
    buildst();
    for(int i=1;i<=m;i++)
    {
        int x,t;
        cin>>x>>t;
        q[i]={x,t};
        c[x].push_back(i);
    }
    dfs1(1,1);
    // cout<<1<<endl;
    for(auto [x,y]:ma)
    {
        // cout<<x<<' '<<y.back()<<endl;
        if(y.size()!=0) ans[y.back()]=1;
    }
    for(int i=1;i<=m;i++) cout<<ans[i];
    cout<<endl;
}
signed main()
{
    // cout<<inf<<endl;
    // cout<<2*fa(2,mod-2)%mod<<endl;
    // cout<<(50000)*(50000)<<endl;
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int T = 1;
    // T=read();
    // cin>>T;
    while(T--)
        solve();

    return 0;
}


 

posted @ 2026-08-17 18:17  lemon_233  阅读(25)  评论(0)    收藏  举报