Codeforces Round#988(A - G)

A. Twice

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 25;
int a[N];//开一个大小为20的桶

int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        memset(a,0,sizeof a);//多组测试数据 初始化
        int n;
        cin >> n;
        for(int i = 1;i <= n;i++)
        {
            int x;
            cin >> x;
            a[x]++;//统计每个数出现的次数
        }
        int score = 0;
        for(int i = 1;i <= 20;i++)
        {
            if(a[i] >= 2) score += a[i] / 2; //最后枚举一遍桶
        }
        
        cout << score << endl;
    }
    return 0;
}

B. Intercepted Inputs

思路:

1.记录所有出现的数的个数

2.预处理出矩阵的长宽

3.判断一下符合矩阵的长宽有没有出现过

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 200010;
int a[N];
typedef pair<int,int> PII;
PII cnt[N];
int idx;

int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        memset(a,0,sizeof a);
        memset(cnt,0,sizeof cnt);
        idx = 0;
        int n;
        cin >> n;
        for(int i = 1;i <= n;i++)
        {
            int x;
            cin >> x;
            a[x] ++;//还是用桶记录每个数出现的次数
        }
        n-=2;//算出矩阵可能的长宽
        for(int i = 1;i <= n/i;i++)
        {
            if(n % i == 0 )//约数是成对出现的
            {
                cnt[idx++] = {i,n/i};//将约数记录在cnt数组当中 (每个cnt是一个pair)
            }
        }
        
        for(int i = 0;i < idx;i++)
        {
            auto t = cnt[i];
            int q = t.first, p = t.second;
            if(a[q] && a[p] || (q == p && a[q] >= 2))//如果符合矩阵的长宽都出现过(特判一下类似16出现2个4的情况)
            {
                cout << q << " " << p << endl;
                break;//保证了题目一定有解
            }
        }
    }
    return 0;
}

C. Superultra's Favorite Permutation

思路:

1.首先要想到n<5是无解的(自己可以枚举一下)

2.其次是如何构造满足题意的序列

3.偶数相加一定为合数 奇数相加也为合数 因此考虑奇数偶数分开放

4.在考虑中间情况(因为当n>=5的时候才有解)那么可以将4 和 5作为奇数偶数的分界线

//一个长度为n的序列
//相邻俩项的和为合数
//序列中不存在相同的数
//2 1 2
//3 1 3 2
//4 1 3 2 4
//5 1 3 5 4 2
//6 1 3 5 4 2 6
//7 2 6 4 5 1 5 7

#include<iostream>
#include<cstring>
using namespace std;

const int N = 200010;
int a[N];

int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        int n;
        cin >> n;
        if(n < 5) puts("-1");
        else
        {
            memset(a,0,sizeof a);
            int idx = 0;
            for(int i = 2;i <= n;i += 2)
            {
                if(i == 4) continue;
                a[idx++] = i;
            }
            a[idx++] = 4;
            a[idx++] = 5;
            for(int i = 1;i <= n;i += 2)
            {
                if(i == 5) continue;
                a[idx++] = i;
            }
            
            for(int i = 0;i < idx;i++) cout << a[i] << " ";
            cout << endl;
        }
        
    }
}

D. Sharky Surfing

思路:(要选择尽可能少的能量石跳过所给区间)

1.肯定是先拿所有能拿的能量石当中 能量最大的一个好

2.我们枚举所给区间 每次将区间左端点的所有能量石加入堆当中(按照能量降序)

3.那么我们如果在后边区间 需要取到之前没选过的能量石 可以直接从堆中拿 (也就是假设我们当时很有远见 提前拿了该能量石)  

#include<bits/stdc++.h>
using namespace std;

const int N = 200010;
int n,m,e;
typedef pair<int,int> PII;
PII bounds[N];


struct Power
{
    int l;
    int v;
    bool operator <(const Power &W)const//重载运算符 使得结构体按价值排序
    {
        return v < W.v;
    }
}power[N];


int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        memset(power,0,sizeof power);
        memset(bounds,0,sizeof bounds);
        priority_queue<int> heap;
        cin >> n >> m >> e;
        
        for(int i = 1;i <= n;i++)
        {
            int l,r;
            cin >> l >> r;
            bounds[i] = {l,r};
        }
        
        for(int i = 0;i < m;i++)
        {
            int l,v;
            cin >> l >> v;
            power[i] = {l,v};
        }
        
        int now = 1,cnt = 0;//目前弹跳能力为1,cnt记录吃了几个糖果
        for(int i = 1,j = 0;i <= n;i++)//枚举n个区间
        {
            while(j < m && power[j].l < bounds[i].first) heap.push(power[j++].v);//将在区间左端点之前 能够吃到的糖果全部入堆
            int need = bounds[i].second - bounds[i].first + 2;//注意如果边界是2 - 5 那么我们需要从1跳到6 需要的power是6-1 = 5 也就是5-2+2
            while(heap.size() && now < need)//当堆不空 并且现在的弹跳能力小于需要的弹跳能力 那么加上堆顶
            {
                cnt ++;
                now+= heap.top();
                heap.pop();
            }
            //如果将堆取完了并且还小于所需值 那么说明无解
            if(now < need)
            {
                cnt = -1;
                break;
            }
        }
        
        cout << cnt << endl;
        
    }
    return 0;
}

E. Kachina's Favorite Binary String

思路:

提问的顺序很关键每次都提问i~n才能确定前一位的值(询问i~n的值记作a[i])

1.a[i] = 0

  (1)   i == 0 那么说明序列一定为1111....000的序列 无法确定1和0的个数

(2)i != 0 && a[i] == 0 说明后面都是1111....000的序列 但是我们a[i-1] 是已知的也就是后面1的个数

2. a[i] == x

(1) a[i-1] == a[i] 说明前一位为1

(2)a[i-1] ! = a[i] 说明前一位为0

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 10010;
int a[N],idx;//a存的每个询问的值a[i]表示i~n的值
int b[N];//b存的是答案
int n;

int ask(int i)
{
    int count;
    cout << "?" << " " << i << " " << n << endl << endl;
    cin >> count;
    return count;
}

bool solve()
{
    for(int i = 1;i < n;i++)
    {
        a[i] = ask(i);//每次询问i~n
        if(i == 1 && a[i] == 0) return false;//如果1~n询问结果为0 那么说明他一定是前面一部分都是1后面一部分都是0
        if(a[i] == a[i-1]) b[i-1] = 1;//如果本次询问跟上次询问的值一样 说明上一位是1
        if(a[i] == 0)//如果本次询问为0 那么说明从第i位开始后面是111...000的序列 1的个数就为a[i-1]的值
        {
            for(int j = 0;j < a[i-1];j++) b[i+j] = 1;
            return true;
        }
    }
    b[n] = 1;//如果到最后一位还没有return说明最后俩位是01直接赋值就行了
    return true;
}


int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        memset(a,0,sizeof a);
        memset(b,0,sizeof b);
        idx = 0;
        cin >> n;
        
        if(solve())
        {
            cout << "! ";
            for(int i = 1;i <= n;i++) cout << b[i];
        }
        else cout << "! IMPOSSIBLE" ;
        cout << endl << endl;
    }
}

F. Ardent Flames

思路:二分攻击次数 扫描线判断是否满足kill k 个怪兽

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 100010;
typedef long long LL;

int h[N];//怪兽的血量
int x[N];//每个怪兽的位置
int n,m,k;

struct seg
{
    
    int pos,p;
    //pos表示区间的左右端点的坐标
    //p表示区间(1)左端点或(-1)右端点
    bool operator <(const seg & W)const
    {
        //先按照坐标大小排序
        //如果坐标相同 那么-1在前面
        return pos < W.pos || pos == W.pos && p < W.p;
    }
}segs[N * 2];


bool check(int cnt)
{
    int idx = 0;
    for(int i = 1;i <= n;i++)
    {
        //如果将怪物处于攻击中心并且施加k次攻击还是无法杀死 那么跳过
        if((LL)cnt * m < (LL)h[i]) continue;
        //下面代表该怪物能被杀死
        //算出攻击中心的范围
        int k = (h[i] + cnt - 1) / cnt;//上取整
        //left表示能杀死怪兽最左边位置
        //right表示能杀死怪兽的最右边位置
        //cnt * (m - | p - x[i]|) >= h[i] (p是攻击中心)(m - |p - x[i] | 是每次造成的伤害)
        // m - | p - x[i] | >= h[i]/cnt(上取整)
        //|p - x[i]| >= h[i]/cnt - m(记作k)
        // x[i] - (m - k) <= p <= x[i] + (m - k)
        int left = x[i] - (m - k);
        int right = x[i] + (m - k);
        segs[idx++] = {left,1};
        segs[idx++] = {right+1,-1};
    }
    sort(segs,segs+idx);
    //扫描线(有点类似差分)
    int count = 0;
    for(int i = 0;i < idx;i++)
    {
        count += segs[i].p;
        if(count >= k) return true;
    }
    return false;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&m,&k);
        int maxv = 0;
        for(int i = 1;i <= n;i++) 
        {
            scanf("%d",&h[i]);
            maxv = max(maxv,h[i]);
        }
        for(int i = 1;i <= n;i++) scanf("%d",&x[i]);
        
        int l = 0,r = maxv+1;// 二分最小的攻击次数
        while(l < r)
        {
            int mid = l + r >> 1;
            if(check(mid)) r = mid;
            else l = mid + 1;
        }
        if(r == maxv + 1) puts("-1");//如果攻击次数为maxv + 1都无法杀死k个怪兽 那么就无解了
        else cout << r << endl;
    }
    return 0;
}

G. Natlan Exploring(线性筛 + 线性dp + 容斥)

对不起过几天补一下思路过程(困了zzzz)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N = 1e6+10,mod = 998244353;
//primes[]存的是质数
//mnp[i]存的是构成i的最小质数
int primes[N],mnp[N],idx;
bool st[N];//线性筛的判重数组
vector<int> allp[N];
int a[N],f[N],sum[N];
//a[]存的题目给的数组
//f[i]从起点走到i的路线


void get_primes()//线性筛
{
    for(int i = 2;i < N;i++)
    {
        if(!st[i])
        {
            primes[idx++] = i;
            mnp[i] = i;
            st[i] = true;
        }
        for(int j = 0;primes[j] * i < N;j++)
        {
            st[primes[j] * i] = true;
            mnp[primes[j] * i] = primes[j];
            if(i % primes[j] == 0) break;
        }
    }
}

void get_allp()//allp[i]存的是构成i的所有质数
{
    for(int i = 2;i < N;i++)
    {
        int u = i;
        while(u != 1)
        {
            int v = mnp[u];//每次取出u的最小质数
            allp[i].push_back(v);
            while(u % v == 0) u /= v;//比如16那么将2除干净
        }
    }
}

int main()
{
    get_primes();
    get_allp();
    
    int n;
    cin >> n;
    for(int i = 0;i < n;i++) cin >> a[i];
    
    f[0] = 1;
    for(int i = 0;i < n;i++)
    {
        vector<int> b;//计算完f[i]之后 用来更新sum的值
        auto &v = allp[a[i]];//取出存a[i]质数的集合
        for(int j = 1;j < 1 << v.size();j++)//枚举质数的组成
        {
            int t = 1,xi = -1;//容斥原理的系数
            for(int k = 0;k < v.size();k++)
            {
                if(j >> k & 1) t*=v[k],xi*=-1;
            }
            f[i] = (f[i] + sum[t] * xi) % mod, b.push_back(t);
        }
        for(auto v : b) sum[v] = (sum[v] + f[i]) % mod;
    }
    
    cout << (f[n-1] + mod)%mod << endl;
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值