Polynomial Round 2022 (Div. 1 + Div. 2, Rated, Prizes!)

A. Add Plus Minus Sign

题意:

给出一个长度为n的01串,你可以在这个01串中添加n - 1个加减号,让整条式子最终的结果绝对值最小

solution

记录当前结果值模拟即可。

B. Coloring

题意:

给出 n , m , k n,m,k n,m,k,将有 n n n个元素的序列染色,要求对任意连续的长度为 k k k的元素序列,这些元素的颜色都不相同。共有 m m m种颜色,第 i i i颜色需要染 a i a_i ai次,保证 ∑ i = 1 m a i = n \sum_{i = 1}^m a_i = n i=1mai=n。判断是否存在一种染色方案满足上述条件。

solution:

将序列分成 n ∣ k n|k nk段,除去这 n ∣ k n|k nk段元素,还剩下 n n%k n个元素。显然对任意一种颜色,染色次数不能超过 ( n ∣ k + 1 ) (n|k + 1) (nk+1)次。染色次数为 ( n ∣ k + 1 ) (n|k + 1) (nk+1)次的颜色个数不能超过 n n%k n次。若满足以上条件,则对于每种颜色贪心地不同地序列段里染色,可以保证有解。

代码:
#include
using namespace std;
void solve()
{
    bool bz = 0;
    int n,m,k;
    scanf("%d %d %d",&n,&m,&k);
    int block = n / k,rem = n % k;
    for(int i = 1;i <= m;i ++)
    {
        int x;
        scanf("%d",&x);
        if(x <= block)
            continue;
        else
        {
            if(x == block + 1 && rem)
                rem --;
            else
                bz = 1;
        }
    }
    if(bz)
    {
        printf("NO\n");
        return;
    }
    printf("YES\n");
}
int main()
{

    int T;
    scanf("%d",&T);
    while(T --)
        solve();
    

}

C. Ice and Fire

题意:

n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1\leq n \leq2\cdot10^5) n(1n2105)个人玩游戏,第 i i i个人的价值为 i i i。规定游戏规则用 0 , 1 0,1 0,1表示。若游戏规则为 0 0 0则价值低的人获胜,若游戏规则为 1 1 1则价值高的人获胜。每一轮选择仍然存活的任意两个人进行比赛,输的人死亡,赢的人存活。游戏一共会进行 n − 1 n - 1 n1轮,最后存活的人为胜利者。

给出一个长度为 n − 1 n - 1 n1 01 01 01 s s s,若有 x x x个人参加游戏,则游戏会进行 x − 1 x - 1 x1 轮,每轮游戏规则为 01 01 01串的前 x − 1 x - 1 x1个字符。

对于所有 x ∈ [ 2 , n ] x\in[2,n] x[2,n],求当有 x x x个人参加游戏时,有多少人可能获得胜利。

solution:

考虑当有 x x x个人参加游戏时,序号为 i i i即价值为 i i i的人怎样才能存活。整个比赛过程可以看做一个长为 x x x的序列,然后不断去除元素。考虑最理想的情况,两种规则可以看做对序列的以下两种操作。

  • 对于规则0,去除一个元素,这个元素至少存在一个比他价值低的元素
  • 对于规则1,去除一个元素,这个元素至少存在一个比他价值高的元素。

我们要判断是否存在一种操作序列使得最后价值为 i i i的元素保留。

发现去除元素这个操作十分麻烦且没有规律,考虑逆向思维,将删除元素的操作转化成增加元素的操作。既然我们要保证最后元素 i i i存活,不妨将问题传化成:一开始有一个元素 i i i,你可以进行以下两种操作

  • 对于规则0,你可以任意添加一个未出现过、当前已存在比它价值小的元素,且小于等于 x x x的元素,使其加入序列
  • 对于规则1,你可以选择任意一个未出现过、当前已存在比它价值大的元素,且大于等于 1 1 1的元素,使其加入序列。

判断是否存在一种操作序列使得可以这个一开始只有元素 i i i的序列还原成包含 1 1 1 x x x的序列。

贪心来想,对于第一次规则为 0 0 0的比赛,我一定会选择让 x x x加入序列(如果可以加入的话),这样可以保证之后规则为 1 1 1的比赛都是合法的(因为 x x x是最大的,所以你可以选择任何元素添加),反之亦然。也就是说,只要我成功使得序列里存在 1 1 1和存在 x x x,那么剩下的操作(无论是 0 0 0还是 1 1 1)都是可以合法的。

因此,只需要记录 01 01 01 s s s的后缀 0 0 0和后缀 1 1 1的个数,对于一个可能胜利的元素, 1 1 1 x x x中比它大的元素个数不能超过后缀 0 0 0的个数,比它小的元素个数不能超过后缀 1 1 1的个数。

代码:
#include
using namespace std;
void solve()
{
    int n;
    scanf("%d",&n);
    string s;
    cin>>s;
    int cnt0 = 0,cnt1 = 0;
    for(int i = 0;i < n - 1;i ++)
    {
        if(s[i] == '0')
            cnt0 ++,cnt1 = 0;
        else
            cnt0 = 0,cnt1 ++;
        int mx = i + 2;
        printf("%d ",mx - max(cnt0,cnt1));
    }
    printf("\n");
}
int main()
{

    int T;
    scanf("%d",&T);
    while(T --)
        solve();
    

}

D. Same Count One

题意:

给出一个 n n n m m m ( n ⋅ m ≤ 1 ⋅ 1 0 6 ) (n\cdot m \leq 1\cdot10^6) (nm1106) 01 01 01矩阵 a a a,你可以进行如下操作:

选择第 i i i行和第 j j j行,以及 p o s pos pos,交换 a i , p o s 和 a j , p o s a_{i,pos}和a_{j,pos} ai,posaj,pos

若干次操作后我们希望这个01矩阵每一行中,值为 1 1 1的列数个数相同。

求最少的操作次数,并输出操作方案。若不存在任何一种操作使得条件满足,则输出 − 1 -1 1

solution

交换并不会改变 1 1 1的个数,令 1 1 1的个数为 t o t tot tot,若 t o t % n ! = 0 tot \% n != 0 tot%n!=0,则无解。

显然,每一行 1 1 1的个数为 t o t / n tot / n tot/n,若当前矩阵不满足情况,必然存在至少一行 1 1 1的个数大于 t o t / n tot/n tot/n,以及至少一行 1 1 1的个数小于 t o t / n tot/n tot/n。贪心一下,将含有 1 1 1的个数较多的行中价值为 1 1 1的列与含有 1 1 1的个数较少的行中价值为 0 0 0的列交换总是最优的。由鸽巢原理,总会存在这样的位置。模拟即可。

代码:
#include 
using namespace std;
#define pii pair<int,int>
#define fi first
#define se second
void solve()
{
    int n,m,cnt = 0;
    scanf("%d %d",&n,&m);
    vector<vector<int>> ar(n,vector<int>(m));
    vector<int> p(n,0);
    for(int i = 0;i < n;i ++)
        for(int j = 0;j < m;j ++)
        {
            scanf("%d",&ar[i][j]),p[i] += ar[i][j],cnt += ar[i][j];
        }
    if(cnt % n != 0)
    {
        printf("-1\n");
        return;
    }
    int stp = 0,exp = cnt / n;
    queue<int> less,more;
    for(int i = 0;i < n;i ++)
    {
        if(p[i] > exp)
            stp += p[i] - exp,more.push(i);
        if(p[i] < exp)
            stp += exp - p[i],less.push(i);
    }
    stp /= 2;
    printf("%d\n",stp);
    while(less.size() || more.size())
    {
        int ls = less.front(),mr = more.front();
        less.pop(),more.pop();
        for(int j = 0;j < m;j ++)
        {
            if(p[ls] == exp || p[mr] == exp)
                break;
            if(ar[ls][j] == 0 && ar[mr][j] == 1)
            {
                ar[ls][j] = 1;
                ar[mr][j] = 0;
                p[ls] ++;
                p[mr] --;
                printf("%d %d %d\n",ls + 1,mr + 1,j + 1);
            }
        }
        if(p[ls] != exp)
            less.push(ls);
        if(p[mr] != exp)
            more.push(mr);
    }
}
int main()
{

    int T;
    scanf("%d",&T);
    while(T --)
        solve();
}

E. Two Chess Pieces

题意:

给你一棵有 n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 ) n(2\leq n \leq 2\cdot 10^5) n(2n2105)个结点的树,树上有两颗棋子。开始时两颗棋子A、B都在结点 1 1 1。你可以进行如下操作:每次操作,你可以移动其中一棵棋子,使其走到相邻的一个结点。在移动过程中,两颗棋子的距离任何时候都不能超过 d ( 2 ≤ d ≤ 2 ⋅ 1 0 5 ) d(2\leq d \leq 2\cdot 10^5) d(2d2105)

对棋子A,给出有 m 1 ( 1 ≤ m 1 ≤ n ) m_1(1\leq m_1 \leq n) m1(1m1n)个目标结点的数组 a a a,你需要以任意顺序使棋子A经过所有目标结点。

对棋子B,给出有 m 2 ( 1 ≤ m 2 ≤ n ) m_2(1\leq m_2 \leq n) m2(1m2n)个目标结点的数组 b b b,你需要以任意顺序使棋子B经过所有目标结点。

最终棋子还必须回到结点 1 1 1,询问最少的操作次数。

solution:

对于A棋子的某个目标结点 x x x,B必须经过 x x x的往上跳 d d d层的祖先,否则就会超出距离。对棋子B同理。

将这些A、B必须经过的附加点加入各自的目标结点序列,然后将两个目标结点序列合并,并按照 d f n dfn dfn序大小排序。可以发现,按照排序后目标结点序列的顺序进行操作是最优的,且总是存在一种操作方案可以满足这个操作序列。之后就可以模拟了。

可能存在某种特殊情况,比如说某个目标结点往上跳 k k k次的祖先不存在。但是由于最终两颗棋子都会回到根节点,所以这种情况并不会影响计数。

代码
#include
using namespace std;
#define N 1000000 + 200
#define pii pair<int,int>
#define fi first
#define se second
int f[N][21];
struct Tree 
{
    std::vector<int> sz, top, dep, fa, dfn,tim,in,out;
    int cur,curr;
    std::vector<std::vector<int>> r;
    Tree(int n) : sz(n + 1), top(n + 1), dep(n + 1), fa(n + 1,0), r(n + 1), dfn(n + 1),cur(0),in(n + 1),out(n + 1) ,curr(0){}
    void addEdge(int u, int v)
    {
        r[u].push_back(v);
        r[v].push_back(u);
    }
    void init() {
        dfsSz(1);
        top[1] = 1;
        dfsHLD(1);
    }
    void dfsSz(int u) 
    {
        if (fa[u] != 0)//删掉连接父亲的边
            r[u].erase(std::find(r[u].begin(), r[u].end(), fa[u]));
        sz[u] = 1;
        for (int &v : r[u])
        {
            fa[v] = u;
            dep[v] = dep[u] + 1;
            dfsSz(v);
            sz[u] += sz[v];
            if (sz[v] > sz[r[u][0]])
                std::swap(v, r[u][0]);
        }
    }
    void dfsHLD(int u) 
    {
        f[u][0] = fa[u];
        for(int i = 1;i <= 20;i ++)
            f[u][i] = f[f[u][i - 1]][i - 1];
        dfn[u] = ++cur;
        in[u] = ++ curr;//欧拉序
        for (int v : r[u]) 
        {
            if (v == r[u][0])
                top[v] = top[u];
            else
                top[v] = v;
            dfsHLD(v);
        }
        out[u] = ++curr;//欧拉序
    }
    int getlca(int u, int v) //多个点lca为dfn最大和最小的两个点的lca
    {
        //如果u是v的祖先或v是u的祖先,那么直接返回祖先,可使时间复杂度降低很多
        if(in[u] <= in[v] && out[u] >= out[v])
            return u;
        if(in[v] <= in[u] && out[v] >= out[u])
            return v;
        //正常重链剖分找lca
        while (top[u] != top[v])
        {
            if (dep[top[u]] > dep[top[v]])
                u = fa[top[u]];
            else 
                v = fa[top[v]];
        }
        if (dep[u] < dep[v])
            return u;
        else
            return v;
    }
    int dis(int u,int v)
    {
        int lca = getlca(u,v);
        return dep[u] + dep[v] - 2 * dep[lca];
    }
    int d_fa(int u,int d)
    {
        int rem = d,nw = u;
        for(int i = 20;i >= 0;i --)
            if(rem & (1<<i))
            {
                rem -= (1<<i);
                if(f[nw][i] == 0)
                {
                    return 1;
                }
                nw = f[nw][i];
            }
        return nw;
    }
};
int main()
{

    int n,m,d;
    vector<int> a;
    vector<int> b;
    scanf("%d %d",&n,&d);
    Tree tr(n);
    for(int i = 1;i <= n - 1;i ++)
    {
        int u,v;
        scanf("%d %d",&u,&v);
        tr.addEdge(u,v);
    }
    tr.init();
    scanf("%d",&m);
    for(int i = 1;i <= m;i ++)
    {
        int x;
        scanf("%d",&x);
        a.push_back(x);
        b.push_back(tr.d_fa(x,d));
    }
    scanf("%d",&m);
    for(int i = 1;i <= m;i ++)
    {
        int x;
        scanf("%d",&x);
        b.push_back(x);
        a.push_back(tr.d_fa(x,d));
    }
    vector<pii> node;
    for(auto v:a)
    {
        node.push_back({v,0});
    }
    for(auto v:b)
    {
        node.push_back({v,1});
    }
    sort(node.begin(),node.end(),[&](pii x,pii y){
        return tr.dfn[x.fi] < tr.dfn[y.fi];
    });
    int resa = 1,resb = 1,stp = 0;
    for(auto v:node)
    {
        if(v.se == 0)
        {
            stp += tr.dis(resa,v.fi);
            resa = v.fi;
        }
        else
        {
            stp += tr.dis(resb,v.fi);
            resb = v.fi;
        }
    }
    stp += tr.dis(resa,1) + tr.dis(resb,1);
    printf("%d\n",stp);
}

F1. Magician and Pigs (Easy Version)

题意:

有一个魔术师,他会进行 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1\leq n\leq2\cdot 10^5) n(1n2105)次操作。操作有 3 3 3种:

  • 1   x 1\ x 1 x:创造一个生命值为 x ( 1 ≤ x ≤ 2 ∗ 1 0 5 ) x(1\leq x \leq 2*10^5) x(1x2105)的小猪
  • 2   x 2\ x 2 x:将所有小猪的生命值减去 x ( 1 ≤ x ≤ 2 ∗ 1 0 5 ) x(1\leq x \leq 2*10^5) x(1x2105)
  • 3 3 3:按顺序重复之前做过的所有操作(包括之前做过的操作3)

如果一只小猪的生命值等于或低于 0 0 0,那么它就会死掉。

给出 n n n次操作,问 n n n次操作时候,还有多少小猪活着。

solution:

模拟可以得到每次操作过后,各种生命值的小猪还剩下多少只,但这样显然会超时。

考虑一次操作 3 3 3会造成什么影响。倘若在进行某次操作 3 3 3之前,我们已知前面所有操作做完之后还剩下多少小猪,以及他们的生命值。假设对于生命值为 h i h_i hi的小猪有 n u m i num_i numi只,此次操作 3 3 3之前的操作会对猪猪造成总共 t o t tot tot的伤害,那么此次操作 3 3 3可以看作将这 n u m i num_i numi只生命值为 h i h_i hi的小猪克隆成 n u m i num_i numi只生命值为 h i − t o t h_i - tot hitot的小猪(原先的小猪)以及 n u m i num_i numi只生命值为 h i h_i hi的小猪(新的小猪)。对于 t o t tot tot值的计算,可以在每次操作 2 2 2时将 x x x值累加,每次操作 3 3 3时将 t o t ∗ 2 tot * 2 tot2。由于 h i h_i hi比较小,至多经过 l o g log log次操作 3 3 3 t o t tot tot就会超过小猪的最大生命值,因此暴力模拟即可。

代码:
#include
using namespace std;
map<long long,long long> M;
const long long mod = 998244353;
int n;
long long mul = 1,inv,ct = 1,tot,dam;
bool bz;
long long qpow(long long x,long long t)
{
    if(t == 0)
        return 1ll;
    else
    {
        if(t % 2)
            return x * qpow(x,t - 1ll) % mod;
        else
        {
            long long tmp = qpow(x,t / 2);
            return tmp * tmp % mod;
        }
    }
}
void add(long long &x,long long y)
{
    x = (x + y) % mod;
}
int main()
{

    scanf("%d",&n);
    inv = qpow(2,mod - 2);
    for(int i = 1;i <= n;i ++)
    {
        int op;
        scanf("%d",&op);
        if(op == 1)
        {
            long long x;
            scanf("%lld",&x);
            //printf("%lld %lld\n",tot + x,tot);
            add(M[tot + x],ct); 
        }
        if(op == 2)
        {
            long long x;
            scanf("%lld",&x);
            tot += x;
        }
        if(op == 3)
        {
            if(tot > 200000)
                continue;
            if(tot == 0)
                mul = mul * 2ll % mod,ct = ct * inv % mod;
            else
            {
                for(int i = 200000 + tot;i > tot;i --)
                {
                    if(M.count((long long)i))
                        add(M[i + tot],M[i]);
                }
                tot *= 2;
            }
            
        }
    }
    long long cnt = 0;
    for(auto v:M)
    {
        if(v.first > tot)
            cnt = (v.second + cnt) % mod;
    }
    cnt = (cnt * mul) % mod;
    printf("%lld\n",cnt);
}

你可能感兴趣的:(cf题解,算法)