CF76题解

CF76

CF76A

链接

CF76A题意

给出一个图( n ≤ 2 × 1 0 2 , m ≤ 5 × 1 0 4 n \le 2\times 10^2,m\le 5\times 10^4 n2×102,m5×104),每条边有两个属性 ( g i , s i ) (g_i,s_i) (gi,si),给出常数 G , S G,S G,S,求一个生成树 T T T 使得 G × max ⁡ ( g i ) + S × max ⁡ ( s i ) G \times \max(g_i) + S \times \max(s_i) G×max(gi)+S×max(si) 最小,求出这个值。

CF76A题解

一眼顶针,鉴定为乱搞题
首先先将所有边按照 g i g_i gi 排序,然后从小到大枚举 s i s_i si,每次只加入 s j > s i s_j > s_i sj>si 的边,然后跑 k r u s k a l kruskal kruskal
但是这样显然是 O ( m 2 ) O(m^2) O(m2) 的,考虑优化。
注意到如果在一次 k r u s k a l kruskal kruskal 的时候,一条边没有用到,那么以后就永远用不到了,因为选这条边显然不会优于之前的某种选法。
而且 n n n 又这么小,每次只需要维护 n − 1 n - 1 n1 条边就好了。
然后就可以对每个 s i s_i si 算出对应的答案啦。
于是就可以 O ( n m ) O(nm) O(nm) 快乐地过啦!

CF776A优化
  1. 注意到刚开始至少需要选 n − 1 n - 1 n1条边,可以先二分一个答案,然后从这个位置开始枚举 s i s_i si
  2. 维护边用的集合可以用 m u l t i s e t multiset multiset

CF76A代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 2e2 + 10, maxm = 5e4 + 10;
int n, m;
int G, S;
struct edge{
    int u, v, g, s, nexte;
    edge(int u = 0,int v = 0,int g = 0,int s = 0,int ne = 0):u(u),v(v),g(g),s(s),nexte(ne){}
    friend bool operator < (edge a,edge b){return a.s < b.s;}
}e[maxm],edg;
int gold[maxm];

int fa[maxn];
int getf(int x){return fa[x] == x ? x : fa[x] = getf(fa[x]);}
multiset<edge> E;
typedef multiset<edge>::iterator iter;
bool check(int mid){
    int cnt = 1;
    for(int i = 1;i <= n;i++){fa[i] = i;}
    for(iter it = E.begin();it != E.end();it++){
        edg = *it;int fu = getf(edg.u), fv = getf(edg.v);
        if(fu == fv || edg.g > mid)continue;
        fa[fu] = fv;cnt++;
    }
    return cnt >= n;
}

signed main(){
    int u, v, g, s, ans = 0x3f3f3f3f3f3f3f3f;
    n =  read(); m = read();G = read(); S = read();
    for(int i = 1;i <= m;i++){
        u = read(); v = read(); g = read(); s = read();
        gold[i] = g;
        e[i] = edge(u,v,g,s);E.insert(e[i]);
    }
    sort(gold + 1,gold + 1 + m);
    int l = 1, r = m, res = -1;
    while(l <= r){
        int mid = l + r >> 1;
        if(check(gold[mid])){r = mid - 1;res = mid;}
        else l = mid + 1;
    }
    if(res == -1){puts("-1");return 0;}
    for(int point = res;point <= m;point++){
        int cnt = 1, maxx = 0;
        for(int j = 1;j <= n;j++)fa[j] = j;
        for(iter it = E.begin();it != E.end();it++){
            edg = *it;int fu = getf(edg.u), fv = getf(edg.v);
            if(edg.g > gold[point])continue;
            if(fu == fv){iter tmp = it;tmp--;E.erase(it);it = tmp;}
            else{fa[fu] = fv;maxx = edg.s;cnt++;}
            if(cnt == n)break;
        }
        if(cnt == n)ans = min(ans,maxx * S + gold[point] * G);
    }
    printf("%lld\n",ans == 0x3f3f3f3f3f3f3f3f ? -1 : ans);
    return 0;
}

CF76B

链接

CF76B题意

在平面上给出两条直线 y = Y 0 y = Y_0 y=Y0 y = Y 1 y = Y_1 y=Y1。在 y = Y 0 y = Y_0 y=Y0 上有 n n n 只老鼠,第 i i i 只老鼠横坐标为 x 1 , i x_{1, i} x1,i,在 y = Y 1 y = Y_1 y=Y1 上有 m m m 个奶酪,第 i i i 个奶酪横坐标为 x 2 , i x_{2, i} x2,i。已知一只老鼠的捕食策略如下:
1. 1. 1. 如果离这只老鼠最近的奶酪有且只有一个,那么这只老鼠会往这个奶酪移动。
2. 2. 2. 如果有多个奶酪离老鼠距离最近,那么这只老鼠会选择向使所有老鼠中挨饿个数最少的奶酪移动。
每只老鼠的移动速度都是一样的,当某些老鼠到达某个奶酪并且当前奶酪还没有被吃掉时,他们会吃掉奶酪并且不再挨饿。如果某个老鼠在到达奶酪时奶酪已经被吃掉了,那么它不会再进行移动,此时我们认为它处于挨饿状态。
请输出最终处于挨饿状态的老鼠个数。

CF76B题解

考虑贪心的找答案,枚举对于每只老鼠,如果只满足第 1 1 1 中情况就没办法了,但是如果满足第 2 2 2 种情况,那么尽可能的让这只老鼠向左找\

CF76B代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e5 + 10,INF = 0x3f3f3f3f3f3f3f3f;
int n, m;
int x[maxn], y[maxn];
int minn[maxn];
signed main(){
    n = read(); m = read();read();read();
    for(int i = 1;i <= n;i++)x[i] = read();
    for(int i = 1;i <= m;i++)y[i] = read();
    int ans = 0;
    y[0] = -INF, y[m + 1] = INF;m++;sort(y,y + m + 1);
    memset(minn,0x3f,sizeof(minn));
    for(int i = 1;i <= n;i++){
        int j = lower_bound(y,y + m + 1,x[i]) - y;j--;
        // printf("j=%lld\n",j);
        int dis = min(x[i] - y[j],y[j + 1] - x[i]);
        if(y[j + 1] - x[i] > dis || (x[i] - y[j] == dis && (minn[j] == INF || minn[j] == dis))){j--;}
        if(minn[j + 1] == INF || minn[j + 1] == dis)ans++;
        minn[j + 1] = min(minn[j + 1],dis);
        // printf("i=%lld : %lld\n",i,ans);
    }
    printf("%lld\n",n - ans);
    return 0;
}

CF76C

链接

CF76C题意

给出一个字符串 S S S (字符种类少于 22 22 22 种)和一个代价上限 t t t
同时给出一个数组 a i a_i ai 表示删除第 i i i 种字符的花费,一个矩阵 c o s t i , j cost_{i,j} costi,j 表示删除字符后的字符串 T T T 中每对相邻的字母需要花费 c o s t T i , T i + 1 cost_{T_i,T_{i+1}} costTi,Ti+1
求出最终花费小于代价上限的方案

CF76C题解

首先思考下暴力怎么写。
每次暴力枚举删除的字符集合,然后再扫一遍相邻的花费。
时间复杂度 O ( 2 k n ) O(2^kn) O(2kn) 显然过不了。\

发现复杂度瓶颈在于计算相邻花费,思考如何优化。
不难发现,如果想让一对点对 ( i , j ) (i,j) (i,j) 相邻,必须删除所有两者之间的字符,并且这两者之间不能有和这两个相同的字符
然后就非常的状压
f S f_S fS 表示删除的字符集合是 S S S 时的花费
首先让 f i = ∑ j ∈ i a j f_i = \sum_{j\in i}a_j fi=jiaj
记录 s t a j sta_j staj 表示到前面的字符 j j j 之间需要删除哪些字符(初始为 − 1 -1 1
然后枚举 S S S 中的每一位 i i i\

  1. 如果 s t a j ≠ − 1 sta_j \neq -1 staj=1
    1. 如果 S i ∉ s t a j S_i\notin sta_j Si/staj j ∉ s t a j j\notin sta_j j/staj
      f s t a j + = c o s t j , S i f_{sta_j} += cost_{j,S_i} fstaj+=costj,Si
      但是这样统计的话会让删除了 j j j S i S_i Si 的情况也加了这种贡献,故
      f s t a j ∣ ( 1 < < S i ) − = c o s t j , S i f_{sta_j | (1<<S_i)}-= cost_{j,S_i} fstaj(1<<Si)=costj,Si
      f s t a j ∣ ( 1 < < j ) − = c o s t j , S i f_{sta_j | (1<<j)}-= cost_{j,S_i} fstaj(1<<j)=costj,Si
      但是同时删除了 j , S i j,S_i j,Si 的情况减了两次这个贡献,故
      f s t a j ∣ ( 1 < < j ) ∣ ( 1 < < S i ) + = c o s t j , S i f_{sta_j | (1<<j) | (1<<S_i)}+= cost_{j,S_i} fstaj(1<<j)(1<<Si)+=costj,Si
    2. s t a j ∣ = ( 1 < < S i ) sta_j |= (1<<S_i) staj=(1<<Si)\

这一位枚举完了之后记得把 s t a S i = 0 sta_{S_i} = 0 staSi=0
那么最终统计答案的时候先让 f i = f i + ∑ j ∈ i f i x o r j f_i = f_i+\sum_{j\in i}f_{ixorj} fi=fi+jifixorj
然后枚举每一种删除方式集合 s t a sta sta,如果 f s t a ≤ t f_{sta}\le t fstat s t a sta sta 是所有字符构成的集合的真子集,那么 a n s + + ans++ ans++

CF76C细节

这道题虽说了只含有前 k k k 个字符,可没说这 k k k 个字符都有吃我四发提交

CF76C代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e5 + 10,maxk = 25;
int f[1 << maxk - 2], n, K, T;
char ch[maxn];
int a[maxn];
int cost[maxk][maxk];
int t[maxk], sta[maxk];
signed main(){
    scanf("%lld%lld%lld",&n,&K,&T);
    int all = 0;
    scanf("%s",ch + 1);for(int i = 1;i <= n;i++){a[i] = ch[i] - 'A';all |= (1 << a[i]);}
    for(int i = 1;i <= K;i++){scanf("%lld",&t[i]);f[1 << (i - 1)] += t[i];}
    for(int i = 1;i <= K;i++)
        for(int j = 1;j <= K;j++)
            scanf("%lld",&cost[i - 1][j - 1]);
    memset(sta,-1,sizeof(sta));
    for(int i = 1;i <= n;i++){
        for(int j = 0;j < K;j++)
            if(sta[j] >= 0){
                if(!((sta[j] >> a[i]) & 1) && !((sta[j] >> j) & 1)){
                    f[sta[j]] += cost[j][a[i]];
                    f[sta[j] | (1 << j)] -= cost[j][a[i]];
                    f[sta[j] | (1 << a[i])] -= cost[j][a[i]];
                    f[sta[j] | (1 << j) | (1 << a[i])] += cost[j][a[i]];
                }
                sta[j] |= (1 << a[i]);
            }
        sta[a[i]] = 0;
    }
    int ans = 0;
    for(int i = 0;i < K;i++)
        for(int j = 0;j < 1 << K;j++)
            if((j >> i) & 1)f[j] += f[j ^ (1 << i)];
    for(int i = 0;i < 1 << K;i++)
        if(f[i] <= T && i != all && (i & all) == i){ans++;}
    printf("%lld\n",ans);
    return 0;
}

CF76D

链接

CF76D题意

给你两个数 A , B A,B A,B,求一对满足 X ⊕ Y = B , X + Y = A X \oplus Y=B,X+Y=A XY=B,X+Y=A,且令 X X X 尽可能小,无解输出 − 1 -1 1

CF76D题解

这啥啊
可知 A − B A-B AB 其实就是 A   a n d   B A \space and \space B A and B 再向左移一位的结果,让 X ← A − B 2 X \gets \frac{A-B}{2} X2AB 显然最优
然后特判下 A < B A<B A<B 或者 2 ∤ ( A − B ) 2\nmid(A-B) 2(AB) 的时候无解就没了

CF76D代码

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
signed main(){
    ull a, b;
    cin >> a >> b;
    if(a < b || (a - b) % 2 != 0)cout << -1 << endl;
    else cout << (a - b) / 2 << " " << a - (a - b) / 2 << endl;
    return 0;
}

CF76E

链接

CF76E题意

n ( n ≤ 1 0 5 ) n(n\le 10^5) n(n105) 个点 ( x i , y i ) (x_i,y_i) (xi,yi),求两两点距离的平方和

CF76E题解

先看横坐标
手推一下柿子:
∑ i = 1 n ∑ j = i + 1 n ( x i − x j ) 2 = ∑ i = 1 n ∑ j = 1 n x i 2 + x j 2 − 2 × x i × x j = ( n − 1 ) × ∑ i = 1 n x i 2 − ∑ i = 1 n x i × [ ( ∑ j = 1 n x j ) − x i ] \sum_{i=1}^n\sum_{j=i+1}^n(x_i-x_j)^2=\sum_{i=1}^n\sum_{j=1}^nx_i^2+x_j^2-2\times x_i \times x_j=(n-1)\times \sum_{i=1}^nx_i^2-\sum_{i=1}^nx_i\times [(\sum_{j=1}^nx_j) - x_i] i=1nj=i+1n(xixj)2=i=1nj=1nxi2+xj22×xi×xj=(n1)×i=1nxi2i=1nxi×[(j=1nxj)xi]
纵坐标同理
然后就没了

CF76E代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e5 + 10;
int n;
int x[maxn], y[maxn];
int sumx, summx, sumy,summy;
signed main(){
    n = read();
    for(int i = 1;i <= n;i++){
        x[i] = read();y[i] = read();
        sumx += x[i];summx += (x[i] * x[i]);
        sumy += y[i];summy += (y[i] * y[i]);
    }
    int ans = 0;
    for(int i = 1;i <= n;i++){
        ans -= x[i] * (sumx - x[i]);
        ans -= y[i] * (sumy - y[i]);
    }
    ans += (n - 1) * (summx + summy);
    printf("%lld\n",ans);
    return 0;
}

CF76F

链接

CF76F题意

在一个数轴上有 n n n 个事件,每个事件的坐标是 x i x_i xi,会在 t i t_i ti 这一时刻出现(之后消失),请问

  • 如果从原点在 0 0 0 时刻出发,那么最多能看到多少个事件
  • 如果可以从任意位置出发,那么最多能看到多少个事件

CF76F题解

首先对于一对点 ( i , j )   ( t i > t j ) (i,j)\space(t_i > t_j) (i,j) (ti>tj),如果从 j j j t j t_j tj 时刻出发,能看到 i i i 的事件的条件

  • x i > x j x_i>x_j xi>xj
    j j j 能赶到 i i i    ⟺    \iff − x j + v × t j ≤ − x i + v × t i -x_j+v\times t_j\le -x_i+v\times t_i xj+v×tjxi+v×ti
  • x i < x j x_i<x_j xi<xj
    j j j 能赶到 i i i    ⟺    \iff x j + v × t j ≤ x i + v × t i x_j+v\times t_j\le x_i+v\times t_i xj+v×tjxi+v×ti

发现一个问题,就是将上面两个柿子推导一下

  • x i > x j x_i>x_j xi>xj
    j j j 能赶到 i i i    ⟺    \iff − x j + v × t j ≤ − x i + v × t i -x_j+v\times t_j\le -x_i+v\times t_i xj+v×tjxi+v×ti    ⟺    \iff 2 x j − x j + v × t j ≤ 2 x i − x i + v × t i 2x_j-x_j+v\times t_j\le 2x_i-x_i+v\times t_i 2xjxj+v×tj2xixi+v×ti    ⟺    \iff x j + v × t j ≤ x i + v × t i x_j+v\times t_j\le x_i+v\times t_i xj+v×tjxi+v×ti
  • x i < x j x_i<x_j xi<xj
    j j j 能赶到 i i i    ⟺    \iff x j + v × t j ≤ x i + v × t i x_j+v\times t_j\le x_i+v\times t_i xj+v×tjxi+v×ti    ⟺    \iff x j − 2 x j + v × t j ≤ x i − 2 x i + v × t i x_j-2x_j+v\times t_j\le x_i-2x_i+v\times t_i xj2xj+v×tjxi2xi+v×ti    ⟺    \iff − x j + v × t j ≤ − x i + v × t i -x_j+v\times t_j\le -x_i+v\times t_i xj+v×tjxi+v×ti

也就是如果让 j ← i j \gets i ji 需要让上面两个柿子都成立
不妨设 a i = − x i + v × t i , b i = x i + v × t i a_i=-x_i+v\times t_i,b_i=x_i+v\times t_i ai=xi+v×ti,bi=xi+v×ti
如果按照 a i a_i ai 排序的话,就变成了要找一个最长的子序列使得 b i b_i bi 单调不降
问题就变成了 L I S LIS LIS
特别的,对于强制从原点出发的,因为 a 0 = b 0 = 0 a_0=b_0=0 a0=b0=0,故所有 a i < 0 a_i<0 ai<0 b i < 0 b_i<0 bi<0 的点不能选,特判一下即可

CF76F代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f= -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x=  (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 1e5 + 10;
int n, V;
struct node{
    int pos, times;
    node(int times = 0,int pos = 0):pos(pos),times(times){}
    friend bool operator < (node a,node b){return a.times != b.times ? a.times < b.times : a.pos < b.pos;}
}a[maxn];

struct node1{
    int x, y;
    node1(int x = 0,int y = 0):x(x),y(y){}
    friend bool operator < (node1 a,node1 b){return a.x != b.x ? a.x < b.x : a.y < b.y;}
}b[maxn],tmp[maxn];

int x[maxn], y[maxn];
int que[maxn], r;

signed main(){
    n = read();
    for(int i = 1;i <= n;i++)a[i] = node(read(),read());
    V = read();
    int cnt = 0;
    for(int i = 1;i <= n;i++){
        b[i] = node1(-a[i].pos + V * a[i].times,a[i].pos + V * a[i].times);
        x[i] = -a[i].pos + V * a[i].times; y[i] = a[i].pos + V * a[i].times;
        if(b[i].x > 0 && b[i].y > 0){tmp[++cnt] = b[i];}
    }
    sort(x + 1,x + 1 + n);sort(y + 1,y + 1 + n);

    for(int i = 1;i <= cnt;i++){tmp[i] = node1(lower_bound(x + 1,x + 1 + n,tmp[i].x) - x,lower_bound(y + 1,y + 1 + n,tmp[i].y) - y);}
    sort(tmp + 1,tmp + 1 + cnt);
    int ans1 = 0;r = 0;
    que[0] = -1;que[++r] = 0x3f3f3f3f;
    for(int i = 1;i <= cnt;i++){
        int pos = upper_bound(que,que + r + 1,tmp[i].y) - que;
        if(que[pos] > tmp[i].y)que[pos] = tmp[i].y;
        if(pos >= r){que[++r] = 0x3f3f3f3f;}
    }
    ans1 = r - 1;

    for(int i = 1;i <= n;i++){b[i] = node1(lower_bound(x + 1,x + 1 + n,b[i].x) - x,lower_bound(y + 1,y + 1 + n,b[i].y) - y);}
    sort(b + 1,b + 1 + n);
    int ans = 0;r = 0;
    memset(que,0,sizeof(que));
    que[0] = -1;que[++r] = 0x3f3f3f3f;
    for(int i = 1;i <= n;i++){
        int pos = upper_bound(que,que + r + 1,b[i].y) - que;
        if(que[pos] > b[i].y)que[pos] = b[i].y;
        if(pos >= r){que[++r] = 0x3f3f3f3f;}
    }
    ans = r - 1;
    // for(int i = 1;i <= n;i++)printf("%d %d\n",b[i].x,b[i].y);
    printf("%d %d\n",ans1,ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值