2021.02.15 北师大寒假新生训练
Candies
- 题意: x + 2 x + 4 x + ⋯ + 2 k − 1 x = n x+2x+4x+⋯+2^{k−1}x=n x+2x+4x+⋯+2k−1x=n. It is guaranteed that at least one solution exists. Note that k > 1 k>1 k>1.
- 解法:暴力搜索
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int main() {
int T;
scanf("%d", &T);
while (T--) {
ll N;
scanf("%lld", &N);
ll k = 2;
while (N % ((1LL << k) - 1) != 0) k++;
printf("%lld\n", N / ((1LL << k) - 1));
}
return 0;
}
Balanced Array
- 题意:
- a的前 n/2 个元素是偶数
- a的后 n/2 个元素是奇数
- a的所有元素都是互不相同的正整数
- 前半部分的和等于后半部分的和
- 解法:题目保证 n 是偶数。这样构造:2 4 6 8 … 1 3 5 … x
- 找到一个x,使得 2 + 4 + 6 + … = 1 + 3 + 5 + … + x
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 200010;
int ans[maxn];
typedef long long ll;
int main() {
int T;
scanf("%d", &T);
while (T--) {
int N;
scanf("%d", &N);
if (N / 2 % 2 == 1) {
printf("NO\n");
continue;
}
printf("YES\n");
ll res = 0;
for (int i = 1; i <= N / 2; i++) {
ans[i] = 2 * i;
res += ans[i];
}
for (int i = N / 2 + 1; i <= N - 1; i++) {
ans[i] = 2 * (i - N / 2) - 1;
res -= ans[i];
}
ans[N] = res;
for (int i = 1; i <= N; i++) {
printf("%d%c", ans[i], i == N ? '\n' : ' ');
}
}
return 0;
}
Alternating Subsequence
题意:找到最长的+ - + - + - + - + -(+ -交替即可)的序列,求最长序列的最大值。
思路:把序列切成 + - + -… 这样一块儿一块儿的,然后找每块儿的最大值。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int main() {
int T;
scanf("%d", &T);
while (T--) {
int N;
scanf("%lld", &N);
ll last = 0, x, ans = 0;
for (int i = 0; i < N; i++) {
scanf("%lld", &x);
if (!last) {
last = x;
continue;
}
if (last * x > 0) {
last = max(last, x);
}
else {
ans += last;
last = x;
}
}
ans += last;
cout << ans << endl;
}
return 0;
}
Constant Palindrome Sum
- 题意:给定一个长度为 n 的数列,n 为偶数,保证每个元素在 [ 1 , k ] 之间。每次操作可以把某个位置的数字变成 [ 1 , k ] 内的任意数字。要求让这个数列满足:对于所有的 i ∈ [ 1 , n/2 ],a[i] + a[n-i+1] 是一个定值。问最少的操作次数。
- 思路:我们知道了 a i a_i ai 与 a n − i + 1 a_{n-i+1} an−i+1 的值之后,就可以知道他们的和sum,变为 [2,2k]的范围的某一个数需要的操作次数,具体来说,就是令 s u m = a i + a n − i + 1 , L = m i n ( a i , a n − i + 1 ) + 1 , R = m a x ( a i , a n − i + 1 ) + k sum = a_i+a_{n-i+1},L=min(a_i, a_{n-i+1})+1,R=max(a_i, a_{n-i+1})+k sum=ai+an−i+1,L=min(ai,an−i+1)+1,R=max(ai,an−i+1)+k。
- 需要的操作次数
- [ 2 , L − 1 ] [2,L-1] [2,L−1]: 2
- [ L , s u m − 1 ] [L, sum-1] [L,sum−1]: 1
- [ s u m , s u m ] [sum, sum] [sum,sum]: 0
- [ s u m + 1 , R ] [sum+1, R] [sum+1,R]: 1
- [ R + 1 , 2 K ] [R+1, 2K] [R+1,2K]: 2
- 然后,用一个差分数组记录这些数字就好,找到一个最小值。
- 这个题实际上是把某个取值范围映射到一个区间上。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 400010;
int f[maxn], a[maxn];
void add(int L, int R, int c) {
if (L > R) return;
f[L] += c;
f[R + 1] -= c;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
int N, K;
scanf("%d%d", &N, &K);
//memset(f, 0, sizeof f);
fill(f, f + 2 * K + 1, 0);
for (int i = 1; i <= N; i++) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= N / 2; i++) {
int L = min(a[i], a[N - i + 1]) + 1, R = max(a[i], a[N - i + 1]) + K,
sum = a[i] + a[N - i + 1];
add(2, L - 1, 2), add(L, sum - 1, 1);
add(sum + 1, R, 1), add(R + 1, 2 * K, 2);
}
int ans = 1e9;
for (int i = 2; i <= 2 * K; i++) {
f[i] += f[i - 1];
ans = min(f[i], ans);
}
cout << ans << endl;
}
return 0;
}
Weights Distributing
- 图论(队友写的)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
ll dis[3][200010];
ll price[500010];
ll e[1000010],ne[1000010],h[1000010],idx;
ll n,m;
void add(ll a,ll b)
{
e[++idx]=b;
ne[idx]=h[a];
h[a]=idx;
}
void dijkstra(ll start,ll x)
{
priority_queue<PII,vector<PII>,greater<PII> >q;
memset(dis[x],0x3f,sizeof dis[x]);
dis[x][start]=0;
q.push({0,start});
while(q.size())
{
PII t=q.top();
q.pop();
ll dist=t.first,u=t.second;
for(ll i=h[u];i;i=ne[i])
{
ll v=e[i];
if(dis[x][v]>dist+1)
{
dis[x][v]=dist+1;
q.push({dist+1,v});
}
}
}
}
void solve()
{
ll A,B,C;
scanf("%lld%lld%lld%lld%lld",&n,&m,&A,&B,&C);
for(ll i=1;i<=n;i++)
{
h[i]=0;
}
idx=0;
for(ll i=1;i<=m;i++)
scanf("%lld",&price[i]);
sort(price+1,price+1+m);
for(ll i=1;i<=m;i++)
price[i]+=price[i-1];
for(ll i=1;i<=m;i++)
{
ll a,b;
scanf("%lld%lld",&a,&b);
add(a,b);
add(b,a);
}
dijkstra(A,1);
dijkstra(C,2);
dijkstra(B,0);
ll ans=5*1e15;
for(ll i=1;i<=n;i++)
{
ll len1=dis[0][i],len2=dis[1][i],len3=dis[2][i];
if(len1+len2+len3>m)
continue;
ans=min(ans,price[len1]+price[len1+len2+len3]);
}
printf("%lld\n",ans);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
solve();
}
return 0;
}
Restore the Permutation by Sorted Segments
- 题意,构造一个排列,给你一些segment,segment里面的数字在原排列中是挨在一块儿的,而且每个segment右区间下标 r 是从 2 到 N 的。
- 只要确定第一个数字,这个排列就确定了。
- 确定第一个数字后,找到size为2的集合,那么集合中另外一个数字就是所求排列的第二个数。此时,遍历所有集合,如果存在某个集合含第一个数不含第二个数,那么说明当前确定的第一个数是不成立的,break掉。
- 如果成立,那么erase所有集合中的上述第一个数字。那么寻找排列第二个数字的过程和前面是一样的。
- 思路不复杂,代码实现起来稍微复杂一些。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<unordered_set>
using namespace std;
const int maxn = 210;
typedef unordered_set<int> us;
us segs[maxn], nsegs[maxn];
int ans[maxn];
int main() {
int T;
scanf("%d", &T);
while (T--) {
memset(ans, 0, sizeof ans);
for (int i = 0; i < maxn; i++) segs[i].clear();
int N;
scanf("%d", &N);
for (int i = 1; i < N; i++) {
int sz;
scanf("%d", &sz);
for (int j = 0; j < sz; j++) {
int x;
scanf("%d", &x);
segs[i].insert(x);
}
}
for (int i = 1; i <= N; i++) {
ans[1] = i;
bool ok = false;
for (int j = 1; j < N; j++) {
nsegs[j] = segs[j];
}
for (int j = 1; j < N; j++) {
int m = 0;
for (int k = 1; k < N; k++) {
if (nsegs[k].size() == 2 && nsegs[k].count(ans[j])) {
for (auto p : nsegs[k]) {
if (p != ans[j]) m = p;
}
}
}
bool flag = true;
//if (ans[1] == 3) printf("###\n");
for (int k = 1; k < N; k++) {
if (nsegs[k].size() > 1 && nsegs[k].count(ans[j]) && !nsegs[k].count(m)) {
flag = false;
break;
}
}
if (!flag) break;
//if (ans[1] == 3) printf("$$$\n");
ans[j + 1] = m;
for (int k = 1; k < N; k++) {
nsegs[k].erase(ans[j]);
//if (ans[1] == 3) printf("*** %d\n", nsegs[k].size());
}
if (j + 1 == N) {
ok = true;
}
}
if (ok) break;
}
for (int i = 1; i <= N; i++) {
printf("%d%c", ans[i], i == N ? '\n' : ' ');
}
}
return 0;
}
Bamboo Leaf Rhapsody
题意:找到离原点最近的点,求这个距离是多少。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int main() {
int N;
scanf("%d", &N);
int res = 1e9;
for (int i = 0; i < N; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
res = min(res, x * x + y * y + z * z);
}
printf("%.3f\n", sqrt(res));
return 0;
}
Cheat Sheet
- 去重,然后按照字符串的长度排序,依次挑选即可。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<unordered_set>
#include<vector>
using namespace std;
bool cmp(const string& s1, const string& s2) {
return (int)s1.size() < (int)s2.size();
}
unordered_set<string> strs;
vector<string> words;
int main() {
int N, M;
scanf("%d%d", &N, &M);
for (int i = 0; i < M; i++) {
string s;
cin >> s;
strs.insert(s);
}
for (auto p : strs) {
words.push_back(p);
}
sort(words.begin(), words.end(), cmp);
int ans = 0;
for (auto p : words) {
if (N >= (int)p.size()) {
ans++;
N -= p.size() + 1;
}
}
cout << ans << endl;
return 0;
}
A. Archmage
- 题意:一个人,每秒使用技能消耗x魔法值,每秒都可以回复y点魔法值。问m秒可以使用几次技能?
- x <= y,直接输出m
- x > y,由于 x + y <= n,因此不存在爆掉问题,但是存在魔法不足的问题。题意是先使用技能再回复,因此总共有 ( m − 1 ) ∗ y + n (m-1)*y + n (m−1)∗y+n 的魔法可以使用。
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
int main() {
int T;
scanf("%d", &T);
while (T--) {
ll n, m, x, y;
scanf("%lld%lld%lld%lld", &n, &m, &x, &y);
printf("%lld\n", min(m, (n + (m - 1) * y) / x));
}
return 0;
}
Hay Mower
- 由于k比R和C大得多,所以肯定有很多重复的。只需要保存最大的 t i t_i ti 就好。
- 求出来第 ( i , j ) (i,j) (i,j)个格点最后一次修改的时间,即 max(r[i], c[j])。
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
const int maxn = 510;
ll r[maxn], c[maxn];
ll a[maxn][maxn];
ll mul(ll x, ll y) {
return (x % mod) * (y % mod) % mod;
}
int main() {
int N, M, K;
scanf("%d%d%d", &N, &M, &K);
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
scanf("%lld", &a[i][j]);
}
}
char op[5];
ll x, t;
for (int i = 0; i < K; i++) {
scanf("%s%lld%lld", op, &x, &t);
if (op[0] == 'r') r[x] = max(t, r[x]);
else c[x] = max(t, c[x]);
}
ll ans = 0;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
t = max(r[i], c[j]);
ans = (ans + mul(a[i][j], t)) % mod;
}
}
cout << ans << endl;
return 0;
}
Disaster Recovery
- 图论(队友写的)
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
typedef struct
{
ll u,v;
}Edge;
Edge edge[200010];
ll n,m,du[100010],p[100010];
ll fib[100010];
ll ancester(ll x)
{
if(x==p[x])
return x;
return p[x]=ancester(p[x]);
}
int cmp(Edge a,Edge b)
{
if(a.u==b.u)
return a.v<b.v;
return a.u<b.u;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=m;i++)
{
ll a,b;
scanf("%lld%lld",&a,&b);
edge[i].u=max(a,b);
edge[i].v=min(a,b);
}
for(ll i=1;i<=n;i++)
p[i]=i;
sort(edge+1,edge+1+m,cmp);
for(ll i=1;i<=m;i++)
{
ll u=edge[i].u,v=edge[i].v;
ll fu=ancester(u),fv=ancester(v);
if(fu!=fv)
{
p[fu]=fv;
du[u]++;
du[v]++;
}
}
ll ans=0;
for(ll i=1;i<=n;i++)
ans=max(ans,du[i]);
printf("%lld",ans);
return 0;
}
Lottery Tickets
- 为能组成的4的倍数最大是多少。
- 发现,后两位是4的倍数,前面不管怎么凑都是4的倍数,按题意就从大往小以此排列,最后不要忘记去掉前导零。若找不到后两位,就看一位数字行不行(0,4,8)。要是都不行的话就输出-1.
- 细节有点多,一开始容易写错
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int c[10];
vector<int> ans;
int main() {
int T;
scanf("%d", &T);
while (T--) {
ans.clear();
for (int i = 0; i <= 9; i++) scanf("%d", &c[i]);
int m0 = 10, m1 = 10;
//最后能否找到两位数,是4的倍数
for (int i = 0; i <= 96; i += 4) {
int d0 = i % 10, d1 = i / 10;
int mmax = max(m0, m1), mmin = min(m0, m1);
int dmax = max(d0, d1), dmin = min(d0, d1);
if ((d0 == d1 && c[d0] >= 2) || (d0 != d1 && c[d0] && c[d1])) {
if (mmax > dmax || mmax == dmax && mmin > dmin) {
m1 = d1, m0 = d0;
//printf("### %d %d\n", m0, m1);
}
else if (mmax == dmax && mmin == dmin) {
m1 = dmax, m0 = dmin;
}
}
}
if (m0 >= 10 && m1 >= 10) {
if (c[8]) printf("8\n");
else if (c[4]) printf("4\n");
else if (c[0]) printf("0\n");
else printf("-1\n");
continue;
}
c[m0]--, c[m1]--;
ans.push_back(m0), ans.push_back(m1);
//printf("*** %d %d\n", m0, m1);
for (int i = 0; i <= 9; i++) {
for (int j = 0; j < c[i]; j++) {
ans.push_back(i);
}
}
while (ans.size() > 1 && ans.back() == 0) ans.pop_back();
for (int i = ans.size() - 1; i >= 0; i--) {
printf("%d", ans[i]);
}
printf("\n");
}
}
Gentle Jena
- 这道题难点就在 A i A_i Ai 的计算上,其他的都很简单。
- 每次计算 A i A_i Ai 时,相当于 A i − 1 + ∑ j = 1 i f ( j , i ) % m o d A_{i-1}+\sum\limits_{j=1}^if(j, i)\ \%\ mod Ai−1+j=1∑if(j,i) % mod。
- 用单调栈可以写,为什么呢?我们这么来考虑单调栈这样一个性质。在原数组中,若 i < j , a i < a j i<j, a_i < a_j i<j,ai<aj,那么若 a i a_i ai 在栈内,则 a j a_j aj 必然在栈内。那么对于那个题,如果新来一个数字 b n b_n bn,把这个 b n b_n bn 开始往栈里面推,如果推到了栈内元素 id 的后面,那么在 [id+1, n] 这个区间内,最小值都是 b n b_n bn,结果为 b n ∗ ( n − i d ) b_n*(n-id) bn∗(n−id),在 [1, id],结果为 s u m [ i d ] sum[id] sum[id]. s u m [ i ] = [ f ( 1 , i ) + f ( 2 , i ) + . . . + f ( i , i ) ] % m o d sum[i] = \Bigl[f(1,i)+f(2,i)+...+f(i,i)\Bigl]\ \%\ mod sum[i]=[f(1,i)+f(2,i)+...+f(i,i)] % mod
#include<cstring>
#include<algorithm>
#include<iostream>
typedef long long ll;
using namespace std;
const int maxn = 10000010;
const ll mod = 998244353;
int stk[maxn];
ll A[maxn], b[maxn], sum[maxn]; //sum[i] 维护的是 f(1,i)+f(2,i)+...+f(i,i).
int main() {
ll n, p, x, y, z, b1;
cin >> n >> p >> x >> y >> z >> b1;
b[1] = A[1] = sum[1] = b1;
int tt = 0;
stk[++tt] = 1;
for (int i = 2; i <= n; i++) {
b[i] = (x * A[i - 1] % p + y * b[i - 1] % p + z) % p;
while (tt && b[stk[tt]] >= b[i]) tt--;
sum[i] = (b[i] * (ll)(i - stk[tt]) % mod + sum[stk[tt]] % mod);
A[i] = (A[i - 1] + sum[i]) % mod;
stk[++tt] = i;
}
ll ans = 0;
for (int i = 1; i <= n; i++) {
ans ^= A[i];
//printf("*** %lld\n", A[i]);
//printf("### %lld\n", b[i]);
}
cout << ans << endl;
}