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;
}