Logo __vector__ 的博客

博客

标签
暂无

题解:CF385D Bear and Floodlight

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-12 16:40:31

考虑到对于已经选择的灯的集合,调整顺序使得能走的距离更远总是不劣的,可以反证法证明。

令 $f_{s}$ 表示已经使用了 $s$ 集合的灯,此时能走的最远距离。

转移的话,就枚举一个新的未使用的灯。

此时,新的灯的照亮的左边界必然就是 $f_s$,现在需要算出这盏灯照亮的右边界。

此时,已经知道了灯的位置 $(x_1,y_1)$,以及灯光覆盖的左边界 $(k_1,0)$,现在要求出灯光覆盖的右边界 $(k_2,0)$。

现在可以先求出 $(x_1,y_1)$ 指向 $(k_1,0)$ 的极角(极轴为 $x$ 轴正向),可以调用 C++ 函数 atan2(0-y,k-x1) 得到。

随后将这个角度加上 $\lpha$ 就能得到 $(x_1,y_1)$ 指向 $(k_2,0)$ 的极角,令其为 $\theta$。

此时考虑构造一个以 $(x_1,y_1)$ 为中心的单位圆,算出其与穿过 $(x_1,y_1)$ 和 $(k_2,0)$ 的直线的交点的横纵坐标相对于 $(x_1,y_1)$ 为 $(sin(\theta),cos(\theta))$ 。

那么可以得到穿过 $(x_1,y_1)$ 和 $(k_2,0)$ 的直线的斜率是 $\frac{\sin(\theta)}{\cos(\theta)} = \tan(\theta)$。

那么现在可以得到: $$ \tan(\theta)\cdot (k_2-x_1) = -y_1\ -\tan(\theta)x_1=-y_1 - \tan(\theta)\cdot k_2\ x_1 = \frac{y_1}{\tan(\theta)} + k_2\\ k_2 = x_1-\frac{y_1}{\tan(\theta)} $$ 上述式子成功求出了灯光覆盖的右边界,需要注意若 $\theta \ge 0$,那么右边会延伸到无穷远,因为此时灯光覆盖区域大概长这个样子:

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
typedef long double ld;
const ld pi=acos(ld(-1));
ld turn(ld jiaodu){
    return jiaodu*(pi\/ld(180));
}
ld calc(pair<ld,ld> nd,ld deg,ld fir){
    ld xcha=nd.fi-fir;
    ld ycha=nd.se;
    ld len=hypot(xcha,ycha);
    xcha\/=len,ycha\/=len;
    ld curdeg=atan2(-ycha,-xcha);
    curdeg+=turn(deg);
    ld dx=cos(curdeg),dy=sin(curdeg);
\/\/    printf("dx %.10Lf dy %.10Lf\n",dx,dy);
    if(abs(dy)<1e-10){
        if(dx>0)return 1e300;
        return fir;
    }
    if(dy>0){
        return 1e300;
    }
    return max(nd.fi+dx*(nd.se\/(-dy)),fir);
}
const int maxn=25;
int n;
ld l,r;
pair<ld,ld> nd[maxn];
ld a[maxn];
ld f[1<<21];
void solve(int id_of_test){
	scanf("%d%Lf%Lf",&n,&l,&r);
    FOR(i,1,n){
        scanf("%Lf%Lf%Lf",&nd[i].fi,&nd[i].se,&a[i]);
    }
    int all=(1<<n)-1;
    FOR(msk,0,all){
        f[msk]=-1e18;
    }
\/\/    printf("calc = %.10Lf\n",calc(nd[1],a[1],0.00001));
    f[0]=l;
    FOR(msk,0,all){
        if(f[msk]<-1e10)continue;
        FOR(i,1,n){
            if(!(msk&(1<<(i-1)))){
                ckmx(f[msk|(1<<(i-1))],calc(nd[i],a[i],f[msk]));
            }
        }
      \/\/  printf("f[%d] = %.10Lf\n",msk,f[msk]);
    }
    printf("%.9Lf\n",min(f[all],r)-l);
}
int main()
{
	int T;
	T=1;
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}
\/*
1. 对题意的理解能否和样例对的上?  
2. 每一步操作,能否和自己的想法对应上?  
3. 每一步操作的正确性是否有保证?  
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?  
*\/

题解:CF1921G Mischievous Shooter

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-18 14:18:14

Statement

给定一个 $n \times m$ 的矩形,包含 $nm$ 个方格,给定一个正整数 $k$。

有一部分格子包含目标。

现在需要选择一个位置 $(x,y)$,以及一个方向(可选范围:右下,右上,左上,左下)。选中之后,选中方向内所有与 $(x,y)$ 曼哈顿距离不超过 $k$ 的格子内的目标均被击中。

提示:$k=3$ 可能击中的区域的形态如下:

07ae9ceed185244b94a445086f5cae84fbf84168

$n\times m \le 10^5,k \le 10^5$。

首先 $4$ 种方向如果分类讨论难度会上天。

此时注意到,任意一种合法的操作,均可以通过旋转矩形,对应到另一个方向的某个合法操作。

同时旋转矩形后执行的任意合法操作,总是等价于旋转前的某一个合法操作。

所以,可以仅保留一种选择方向,对矩形的 $4$ 种旋转方式分别计算答案取最大值就可以了。

此处选择什么方向无所谓,我在此以右上方向(即第一张图片)为例。

由于选择区域的形状比较特别,目标总数量难以直接通过前缀和等方式快速得到,所以考虑递推。

令 $w(i,j)$ 表示以 $(i,j)$ 为左下角的区域的目标总数。

考虑其和 $w(i-1,j)$ 的关系。

对于 $k=3$ 大概是下面的样子:

1921G 第二张配图

红色加号代表新增区域,蓝色减号代表减少的区域。

可以发现增加的红色区域是第 $i$ 行的 $j \sim j+k$ 列。

减少的蓝色区域是 $(i-1,j+k)$ 为结尾,$(i-k-1,1)$ 为开始的对角线。

那么对每一行预处理从左到右的前缀和,以及预处理左上到右下的对角线前缀和,就可以 $O(1)$ 从 $w(i-1,j)$ 转化到 $w(i,j)$ 了。

题解:CF520E Pluses everywhere

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-18 14:23:23

考虑分别计算每一位产生的贡献,加起来得到总和。

假设当前是从左边数第 $x$ 位,那么分类讨论两种情况。

  • $i \le n-x-1$。

    当前作为 $10^i$ 被计入答案的次数为 $\binom{n-1-i-1}{k-1} = \binom{n-i-2}{k-1}$。

    即,剩余可以放加号的空位的数量是 $n-1-i-1$,然后由于右边固定了一个加号做结尾,所以只需要再放 $k-1$ 个加号。

  • $i = n-x$。

    当前作为 $10^i$ 被记入答案的次数为 $\binom{x-1}{k}$。

    即,位置 $1$ 到 $x$ 有 $x-1$ 个空位,填 $k$ 个加号,且当前数字段一直到结尾。

考虑预处理 $p_i = \sum\limits_{j=1}^i \binom{n-j-2}{k-1}10^j$。

然后枚举每一位就能 $O(1)$ 算出贡献了。

题解:CF496E Distributing Parts

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-18 15:24:36

现在处理左端点最小的角色,与之配对的演员需要符合以下两个要求:

  1. 演员的左端点小于等于角色的左端点。
  2. 演员的右端点大于等于角色的右端点。

在满足条件 1 的前提下,事实上演员的左端点是什么已经无所谓了,因为如果一个演员的左端点小于等于当前角色的左端点,那么说明该演员的左端点也同时小于等于其他所有角色的右端点,那么就说明所有满足条件一的演员的左端点事实上互相没有优劣之分。

既然对于满足条件一的演员集合,左端点是什么不重要,那么就关心右端点了。

此时,必定选择右端点大于等于当前角色的演员里面,右端点最小的那个(当然记得满足条件一)。

考虑怎么写程序,可以用 set 实现,不过有更简单的办法,就是将演员和角色一起按左端点升序排列,如果左端点一致就把演员放在前面。

然后,每个角色左边的所有演员就是所有左端点小于等于其的演员。

#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
const int maxn=1e5+5;
int n,m;
struct INFO{
    bool type;
    int l,r,k;
    int id;
}info[maxn*2];
void solve(int id_of_test){
	read(n);
    FOR(i,1,n){
        info[i].type=0;
        read(info[i].l);
        read(info[i].r);
        info[i].id=i;
    }
    read(m);
    FOR(i,1,m){
        info[i+n].type=1;
        read(info[i+n].l);
        read(info[i+n].r);
        read(info[i+n].k);
        info[i+n].id=i;
    }
    sort(info+1,info+n+m+1,[&](INFO& a,INFO& b){
        if(a.l!=b.l)return a.l<b.l;
        if(a.r!=b.r)a.r>b.r;
        return a.type>b.type;
    });
    vi ans(n+1,0);
    set<pii> rem;
    FOR(i,1,n+m){
        if(info[i].type==1){
            rem.insert(mkpr(info[i].r,i));
        }else{
            auto ptr=rem.lower_bound(mkpr(info[i].r,-1));
            if(ptr==rem.end()){
                puts("NO");
                return;
            }
            int pos=ptr->se;
            ans[info[i].id]=info[pos].id;
            info[pos].k--;
            if(!info[pos].k){
                rem.erase(ptr);
            }
        }
    }
    puts("YES");
    FOR(i,1,n)printf("%d ",ans[i]);
    pln
}

题解:CF1296F Berland Beauty

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-18 15:49:00

Statement

给定了一棵 $n$ 个点的树,每条边都有一个 $1 \sim 10^6$ 之间的整数边权。

有 $m$ 个条件,每个形式如下:

  • $a$ 点到 $b$ 点的路径上的最小边权是 $c$。

要求构造一组合法的边权满足所有条件。

$n,m \le 5000$。

Solution

对于上述条件,首先满足 $a$ 到 $b$ 的路径上不能出现小于 $c$ 的边权。

在此基础上,每个边权应当尽可能小。

所以,为每条边赋一个初始边权 $1$,然后对于每对 $(a,b,c)$(含义见上述描述),将 $a$ 到 $b$ 路径上所有边权对 $c$ 取 max。

如果这个边权方案不符合要求,那么肯定不存在合法方案了。

#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define mkpr make_pair
const int maxn=5005;
int n;
vector<pii> g[maxn];
int val[maxn];
int dep[maxn];
int fa[maxn];
int faid[maxn];
void dfs(int u,int _fa){
    fa[u]=_fa;
    dep[u]=dep[_fa]+1;
    for(auto [v,id]:g[u]){
        if(v==_fa)continue;
        faid[v]=id;
        dfs(v,u);
    }
}
vector<pair<int,int> > rem[maxn];
tuple<int,int,int> ops[maxn];
void solve(int id_of_test){
	read(n);
    FOR(i,1,n)val[i]=1;
    FOR(i,1,n-1){
        int x,y;
        read(x);
        read(y);
        g[x].eb(mkpr(y,i));
        g[y].eb(mkpr(x,i));
    }
    dfs(1,0);
    int m;
    read(m);
    FOR(i,1,m){
        int a,b,v;
        read(a);
        read(b);
        read(v);
        ops[i]=tie(a,b,v);
        vi path;
        while(a!=b){
            if(dep[a]<dep[b])swap(a,b);
            path.eb(faid[a]);
            a=fa[a];
        }
        for(int& eid:path)ckmx(val[eid],v);
    }
    FOR(i,1,m){
        int a,b,v;
        tie(a,b,v)=ops[i];
        vi path;
        while(a!=b){
            if(dep[a]<dep[b])swap(a,b);
            path.eb(faid[a]);
            a=fa[a];
        }
        bool yes=0;
        for(int& eid:path){
           \/\/ printf("i = %d meet = %d\n",i,val[v]);
            if(val[eid]<v){
                yes=0;
                break;
            }else if(val[eid]==v){
                yes=1;
            }
        }
        if(!yes){
            puts("-1");
            return;
        }
    }
    FOR(i,1,n-1){
        printf("%d ",val[i]);
    }
}

题解:CF1343E Weights Distributing

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-18 16:10:35

定义 $dis(i,j)$ 表示从 $i$ 走到 $j$ 的最少边数。

假设 $a \sim b$ 与 $b \sim c$ 的路径没有重合的话,那么只需要 $p$ 中最小的 $dis(a,b)+dis(b,c)$ 个数值,将它们赋到 $a \sim b$ 和 $b \sim c$ 的路径上就可以了。

但是现在的问题就在于路径重合的问题,如果还按照刚才的算法会把答案算大,甚至找到不存在的路径(即 $dis(a,b)+dis(b,c) \gt m$ 的清空)。

考虑枚举重合信息。

枚举 $md$ 作为 $b$ 到 $c$ 的路径上最后一个与 $a \sim b$ 路径重合的节点。

那么,此时路径将分为三个部分:

  • $a \sim md$ 的路径,路径上的边计入一次答案。
  • $md \sim b$ 的路径,路径上的边计入两次答案。
  • $md \sim c$ 的路径,路径上的边计入一次答案。

此时按照最优方式,上述三段路径都按照最短路计算就可以了,即路径长度分别为 $dis(a,md),dis(md,b),dis(md,c)$。

然后,优先把 $p$ 中较小的数值分配给第二部分的路径,然后再分配一,三部分的路径就行了。

尽管有可能因此不再满足路径重合的一些条件,但是这仍然是对的,由于以下两点:

  • 对于最优的路径,一定会被上述过程枚举到。
  • 上述枚举的路径一定是一条合法的路径。

对于最短路的计算,bfs 预处理 $a,c$ 到其他所有点的最小边数就可以了。

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
const int maxn=2e5+5;
int n,m,a,b,c;
ll p[maxn],pre[maxn];
ll w(int l,int r){
    return pre[r]-pre[l-1];
}
vi g[maxn];
int disa[maxn],disb[maxn],disc[maxn];
void bfs(int s,int* dis){
    FOR(i,1,n)dis[i]=1e9;
    queue<int> q;
    q.push(s);
    dis[s]=0;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int& v:g[u]){
            if(dis[v]>dis[u]+1){
                dis[v]=dis[u]+1;
                q.push(v);
            }
        }
    }
}
void clear(){
    FOR(i,1,n){
        g[i].clear();
    }
}
void solve(int id_of_test){
	read(n);
    read(m);
    read(a);
    read(b);
    read(c);
    FOR(i,1,m){
        read(p[i]);
    }
    sort(p+1,p+m+1);
    FOR(i,1,m){
        pre[i]=pre[i-1]+p[i];
    }
    FOR(i,1,m){
        int u,v;
        read(u);
        read(v);
        g[u].eb(v);
        g[v].eb(u);
    }
    bfs(a,disa);
    bfs(b,disb);
    bfs(c,disc);
    ll ans=1e18;
    FOR(mid,1,n){
        \/\/ a-->b
        \/\/ b-->c
        \/\/ 上述路径共同都要到达 mid
        int len=disb[mid];
        if(len+disa[mid]+disc[mid]>m)continue;
        ll res=pre[len]*2;
        res+=w(len+1,len+disa[mid]+disc[mid]);
    \/\/    printf("mid = %d res = %lld\n",mid,res);
        ckmn(ans,res);
    }
    printf("%lld\n",ans);
    clear();
}
int main()
{
	int T;
	read(T);
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}
\/*
1. 对题意的理解能否和样例对的上?  
2. 每一步操作,能否和自己的想法对应上?  
3. 每一步操作的正确性是否有保证?  
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?  
*\/

题解:CF754D Fedor and coupons

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-18 16:32:19

假设答案的左端点是 $x$,那么右端点 $y$ 怎么求?

明显的,在左端点小于等于 $x$ 的区间里面选择 $k$ 个右端点最大的,其中右端点第 $k$ 大的区间的右端点就是 $y$。

即使这 $k$ 个区间没有一个左端点等于 $x$ 的也无所谓,这样只会让答案变大,但是按照这个方法枚举一遍所有可能的 $x$,最优解是一定会枚举到的。

为了快速实现这个程序,可以将升序枚举答案的左端点 $x$,每枚举一个新的 $x$ 就将左端点为 $x$ 的区间加入候选集合,然后候选集合删掉右端点最小的,直到大小不超过 $k$,可以用 set 维护这个集合。

$x$ 的数量也只有不超过 $n$ 个,因为任意一个合法的 $x$ 必然是某个区间的左端点。

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
const int maxn=3e5+5;
int n,k;
struct PII{
    int fi,se,id;
}seq[maxn];
void solve(int id_of_test){
	read(n);
    read(k);
    FOR(i,1,n){
        read(seq[i].fi);
        read(seq[i].se);
        seq[i].id=i;
    }
    sort(seq+1,seq+n+1,[&](PII& a,PII& b){
        if(a.fi!=b.fi)return a.fi<b.fi;
        return a.se<b.se;
    });
    multiset<pii> s;
    int ans=0,ansid;
    FOR(i,1,n){
        if(s.size()>=k){
            s.erase(s.begin());
        }
        s.insert(mkpr(seq[i].se,seq[i].id));
        if(s.size()==k){
            int j=s.begin()->fi;
            if(j-seq[i].fi+1>ans){
                ans=j-seq[i].fi+1;
                ansid=i;
            }
        }
    }
    if(!ans){
        printf("%d\n",ans);
        FOR(i,1,k)printf("%d ",i);
        pln
        return;
    }
    s.clear();
    FOR(i,1,n){
        s.insert(mkpr(seq[i].se,seq[i].id));
        if(s.size()>k)s.erase(s.begin());
        if(i==ansid){
            printf("%d\n",ans);
            for(auto v:s)printf("%d ",v.se);
            pln
            return;
        }
    }
}
int main()
{
	int T;
	T=1;
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}
\/*
1. 对题意的理解能否和样例对的上?  
2. 每一步操作,能否和自己的想法对应上?  
3. 每一步操作的正确性是否有保证?  
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?  
*\/

题解:P13345 [EGOI 2025] IMO

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-26 20:00:09

实际上已知最终的顺序。

考虑按照最终的顺序逐个扫描。

合法的前提是相邻后一项的最大成绩小于等于(能否等于得看 id)前一项的最大成绩。

$f_{i,j}$ 表示已经确定了前 $i$ 个选手的方案,总共隐藏 $j$ 题,此时最后一个人的最大的公开成绩。

可以粗略的估计,所有选手隐藏题目个数的总和不超过 $M$。

假设隐藏了 $M+1$ 个题目。

首先,初始情况下最高分减去最低分最多为 $M \cdot K$。

若隐藏了 $M+1$ 个问题,那么意味着中间的差值减少了 $(M+1)\cdot K$,无论如何都不能保证原来的顺序。

现在需要知道每个选手有哪些方案。

$g_{i,j,k}$:当前选手考虑了前 $i$ 道题,公开了 $j$ 个,此时公开总和为 $k$ 是否可行。

转移:逐个扫描题目:

  • $g_{i+1,j+1,k+v_{i+1}} \leftarrow g_{i,j,k}$
  • $g_{i+1,j,k}\leftarrow g_{i,j,k}$。

其中 $v_i$ 表示当前选手对于第 $i$ 题的分数。

暴力转移复杂度将会是 $O(m^3k)$ 的。

加上 bitset 优化后变为 $O(\frac{m^3k}{\omega})$ 。

对于 $f$ 本身的转移,需要枚举下一个人的公开成绩,以及公开题目个数。

那么 $f$ 的求解总复杂度将会是 $O(nm^3k)$。

总复杂度 $O(nm^3k+\frac{m^3k}{\omega}) = O(nm^3k)$,期望 72pts,irris 说开 O3 优化能过。

考虑哪些状态有效。

重定义状态 $g_{i,j,s}$ 表示当前选手考虑了前 $i$ 道题,公开了 $j$ 个,真实总和减去隐藏总和的值为 $s$ 是否可行。

令 $id$ 表示当前选手编号,注意到 $s \ge score_{id+1}$。

也就是说,$s \in [score_{id+1},score_{id}]$。

仅保留有效的 $s$,那么注意到所有选手的 $g$ 的有效状态数加起来仅有 $O(m^2\sum\limits_{i=1}^n(score_i-score_{i+1})) = O(m^3k)$,其中 $score_{n+1}$ 视为 $0$。

再分析一下 $f$ 此时的转移复杂度。

每一层分别加和,得到 $\sum\limits_{i=1}^n (score_i-score_{i+1})m^2 = O(m^3k)$。

再算上一开始的排序,总复杂度将是 $O(n\log n + m^3k)$。

LCA NOIP模拟 2025.9.25

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-09-26 20:15:18

T1

枚举左端点,考虑怎么确定右端点。

首先要满足两个数字的位数一致,所以考虑先通过位数筛掉一些右端点。

注意到随着选择区间的增加,第一个数字的长度单调不增。

另外,选择的区间的总和的位数每增加一位,都需要增加很多选择的数字才可以。

对于同一个 $l$,最多连续大约 $6$ 个 $r$ 对应的最终位数是一致的。

也就是说,可能的右端点最多只有 $6$ 个,先二分找出可能的右端点区间(二分的根据是结果的位数),然后暴力枚举右端点判定。

T2

一开始,所有艺术家的可能作品集合都是 $1,2,\cdots,n$。

第一次操作后,划分为两个集合,集合内部每个艺术家的可能作品集合都一致,即所有集合内艺术家的作品都有可能是自己的作品。

第二次操作后,对于每个集合,再次根据本次的分组情况进行划分成两个集合。

一个集合划分之后如果大小变为 $1$,那么其就确定了。

T3

考虑二分答案,令答案为 $t$,首先保证二分上界不能超过最小区间的长度。

那么假设左端点为 $l$,右端点为 $r$,即 $r-l+1=t$。

答案将会是 $\sum\limits_{i=1}^n\max(0,L_i-l)+\max(0,r-R_i)$

上述式子改一下变为:
$$ \sum\limits_{i=1}^n(\max(0,L_i-l)+\max(0,l+t-1-R_i)) $$ 令 $U_i = R_i-t+1$,上式变为:
$$ \sum\limits_{i=1}^n(\max(0,L_i-l)+\max(0,l-U_i)) $$

$$ \sum\limits_{L_i \ge l}L_i-l + \sum\limits_{U_i \le l}l-U_i $$

从一开始,$l$ 不断增加,答案会先减少再上升。

考虑三分 $l$。

T4

实际上已知最终的顺序。

考虑按照最终的顺序逐个扫描。

合法的前提是相邻后一项的最大成绩小于等于(能否等于得看 id)前一项的最大成绩。

$f_{i,j}$ 表示已经确定了前 $i$ 个选手的方案,总共隐藏 $j$ 题,此时最后一个人的最大的公开成绩。

可以粗略的估计,所有选手隐藏题目个数的总和不超过 $M$。

假设隐藏了 $M+1$ 个题目。

首先,初始情况下最高分减去最低分最多为 $M \cdot K$。

若隐藏了 $M+1$ 个问题,那么意味着中间的差值减少了 $(M+1)\cdot K$,无论如何都不能保证原来的顺序。

现在需要知道每个选手有哪些方案。

$g_{i,j,k}$:当前选手考虑了前 $i$ 道题,公开了 $j$ 个,此时公开总和为 $k$ 是否可行。

转移:逐个扫描题目:

  • $g_{i+1,j+1,k+v_{i+1}} \leftarrow g_{i,j,k}$
  • $g_{i+1,j,k}\leftarrow g_{i,j,k}$。

其中 $v_i$ 表示当前选手对于第 $i$ 题的分数。

暴力转移复杂度将会是 $O(m^3k)$ 的。

加上 bitset 优化后变为 $O(\frac{m^3k}{\omega})$ 。

对于 $f$ 本身的转移,需要枚举下一个人的公开成绩,以及公开题目个数。

那么 $f$ 的求解总复杂度将会是 $O(nm^3k)$。

总复杂度 $O(nm^3k+\frac{m^3k}{\omega})$,期望 72pts,irris 说开 O3 优化能过。

考虑哪些状态有效。

重定义状态 $g_{i,j,s}$ 表示当前选手考虑了前 $i$ 道题,公开了 $j$ 个,真实总和减去隐藏总和的值为 $s$ 是否可行。

令 $id$ 表示当前选手编号,注意到 $s \ge score_{id+1}$。

也就是说,$s \in [score_{id+1},score_{id}]$。

仅保留有效的 $s$,那么注意到所有选手的 $g$ 的有效状态数加起来仅有 $O(m^2\sum\limits_{i=1}^n(score_i-score_{i+1})) = O(m^3k)$,其中 $score_{n+1}$ 视为 $0$。

再分析一下 $f$ 此时的转移复杂度。

每一层分别加和,得到 $\sum\limits_{i=1}^n (score_i-score_{i+1})m^2 = O(m^3k)$。

再算上一开始的排序,总复杂度将是 $O(n\log n + m^3k)$。

题解:P11340 [COI 2019] TENIS

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-10-04 11:44:43

  • 结论一:

    能成为冠军的人,在任意一张地图的排行榜上构成一个前缀。

  • 证明:

    假设某一张地图上存在 $i \lt j$,且排名为 $i$ 的人无法成为冠军,但排名为 $j$ 的人可以成为冠军,那么由排名为 $j$ 的人击败除了排名为 $i$ 的人以外的所有的人,然后再由排名为 $i$ 的人击败排名为 $j$ 的人,那么排名为 $i$ 的人就可以成为冠军,与假设矛盾。

  • 结论二:

    三张地图的冠军前缀长度相等。

  • 证明:

    假设三张地图的冠军构成的前缀长度从小到大依次为 $len_1,len_2,len_3$,且 $len_1 \neq len_3$。

    那么,对于长度为 $len_3$ 的冠军前缀,其在另外两个排行榜上同样占据 $len_3$ 个人,所有排名小于等于这些人的,同样构成冠军,那么也就是说 $len_1$ 肯定大于等于 $len_3$,与假设矛盾。

考虑如何确定前缀的长度,假定其为 $len$。

那么任意一个冠军的最差排名不会高于 $len$。

另外,必然存在至少一个冠军的最差排名为 $len$,否则排名为 $len$ 的人无论如何也无法击败任意一个冠军,也就没有排名为 $len$ 的人能成为冠军。

那么现在已经确定了条件:

  • 令选手 $i$ 的最优排名为 $l_i$,最差排名为 $r_i$,那么 $len$ 就是最小的位置,使得不存在任意一个区间左端点小于 $len$,右端点大于等于 $len$。

对于每个位置,记录左端点小于等于其的区间个数减去右端点小于等于其的区间个数。

这个数字肯定是一个非负整数,现在要找的就是第一个 $0$ 的位置。

那么,考虑使用线段树,其支持以下操作:

  • 区间加法。

    修改操作需要用到。

  • 维护区间最小值。

    用于确定区间是否存在 $0$。

  • 二分查找第一个 $0$ 的位置。

    这个需要依赖区间最小值。

一种可以通过的代码实现:

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(auto i=(a);i<=(b);i++)
#define REP(i,a,b) for(auto i=(a);i>=(b);i--)
#define FORK(i,a,b,k) for(auto i=(a);i<=(b);i+=(k))
#define REPK(i,a,b,k) for(auto i=(a);i>=(b);i-=(k))
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
template<class T>
T gcd(T a,T b){
    return !b?a:gcd(b,a%b);
}
template<class T>
T lcm(T a,T b){
    return a\/gcd(a,b)*b;
}
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
#define islower(ch) (ch>='a'&&ch<='z')
#define isupper(ch) (ch>='A'&&ch<='Z')
#define isalpha(ch) (islower(ch)||isupper(ch))
template<class T>
void wrint(T x){
    if(x<0){
        x=-x;
        pc('-');
    }
    if(x>=10){
        wrint(x\/10);
    }
    pc(x%10^48);
}
template<class T>
void wrintln(T x){
    wrint(x);
    pln
}
template<class T>
void read(T& x){
    x=0;
    int f=1;
    char ch=gc;
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=gc;
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=gc;
    }
    x*=f;
}
void ioopti(){
    ios::sync_with_stdio(0);
    cin.tie(0);
}
void io(){
    freopen("champion.in","r",stdin);
    freopen("champion.out","w",stdout);
}
const int maxn=1e5+5;
struct SGT{
    int mn[maxn<<2],lazy[maxn<<2];
    int lef[maxn<<2],rig[maxn<<2];
    void pushup(int pos){
        mn[pos]=min(mn[pos<<1],mn[pos<<1|1]);
    }
    void upd(int pos,int _add){
        mn[pos]+=_add;
        lazy[pos]+=_add;
    }
    void pushdown(int pos){
        if(!lazy[pos])return;
        upd(pos<<1,lazy[pos]);
        upd(pos<<1|1,lazy[pos]);
        lazy[pos]=0;
    }
    void bd(int pos,int l,int r){
        lef[pos]=l,rig[pos]=r;
        if(l==r)return;
        int mid=(l+r>>1);
        bd(pos<<1,l,mid);
        bd(pos<<1|1,mid+1,r);
        pushup(pos);
    }
    void add(int pos,int l,int r,int _add){
        if(lef[pos]>=l&&rig[pos]<=r){
            upd(pos,_add);
            return;
        }
        pushdown(pos);
        int mid=(lef[pos]+rig[pos]>>1);
        if(mid>=l)add(pos<<1,l,r,_add);
        if(mid<r)add(pos<<1|1,l,r,_add);
        pushup(pos);
    }
    int find(int pos){
        if(lef[pos]==rig[pos]){
            return lef[pos];
        }
        pushdown(pos);
        if(mn[pos<<1]==0)return find(pos<<1);
        return find(pos<<1|1);
    }
}sgt;
int n,q;
int rklist[4][maxn];
int pos[4][maxn];
int l[maxn],r[maxn];
void solve(int id_of_test){
	read(n);
    read(q);
    memset(l,0x3f,sizeof l);
    sgt.bd(1,1,n);
    FOR(i,1,3){
        FOR(j,1,n){
            read(rklist[i][j]);
            pos[i][rklist[i][j]]=j;
            ckmn(l[rklist[i][j]],j);
            ckmx(r[rklist[i][j]],j);
        }
    }
    FOR(i,1,n){
        sgt.add(1,l[i],n,1);
        sgt.add(1,r[i],n,-1);
    }
    while(q--){
        int op;
        read(op);
        if(op==1){
            int x;
            read(x);
          \/\/  printf("sgt.find1 = %d\n",sgt.find(1));
            if(min({pos[1][x],pos[2][x],pos[3][x]})<=sgt.find(1)){
                puts("DA");
                continue;
            }
            puts("NE");
        }else{
            int p,a,b;
            read(p);
            read(a);
            read(b);
            sgt.add(1,l[a],n,-1);
            sgt.add(1,r[a],n,1);
            sgt.add(1,l[b],n,-1);
            sgt.add(1,r[b],n,1);
            int &id1=pos[p][a],&id2=pos[p][b];
            swap(rklist[p][id1],rklist[p][id2]);
            swap(id1,id2);
            l[a]=min({pos[1][a],pos[2][a],pos[3][a]});
            r[a]=max({pos[1][a],pos[2][a],pos[3][a]});
            l[b]=min({pos[1][b],pos[2][b],pos[3][b]});
            r[b]=max({pos[1][b],pos[2][b],pos[3][b]});
            sgt.add(1,l[a],n,1);
            sgt.add(1,r[a],n,-1);
            sgt.add(1,l[b],n,1);
            sgt.add(1,r[b],n,-1);
        }
    }
}
int main()
{
    \/\/ io();
	int T;
	T=1;
	FOR(_,1,T){
		solve(_);
	}
	return 0;
}
\/*
1. 对题意的理解能否和样例对的上?  
2. 每一步操作,能否和自己的想法对应上?  
3. 每一步操作的正确性是否有保证?  
4. 是否考虑到了所有的 case?特别是极限数据。
5. 变量的数据类型是否与其值域匹配?
6. 时间复杂度有保证吗?
7. 空间多少 MB?  
*\/
共 318 篇博客