本场链接:[Codeforces Round #698 (Div. 2)]
闲话
超级自闭场
A. Nezzar and Colorful Balls
题目大意:有\(n\)个球序号从\(1\)开始,每个球上带一个权值,所有球的权值按序号排列是非降序列.现在要给每个球画一个颜色,要求每种颜色的球的权值是严格递增的.
思路
由于范围很小,可以直接拿数组记录每个颜色下权值最大的值,每次遍历每个颜色,看一下能不能把当前的球塞到序列末尾就可以了.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 105;
int a[N],c[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
forn(i,1,n) scanf("%d",&a[i]),c[i] = 0;
forn(i,1,n)
{
forn(j,1,n)
{
if(c[j] < a[i])
{
c[j] = a[i];
break;
}
}
}
int res = 0;
forn(i,1,n) if(c[i]) ++res;
printf("%d\n",res);
}
return 0;
}
B. Nezzar and Lucky Number
题目大意:认为一个数是牛逼的,当且仅当这个数的十进制表示下存在一个数位为\(d\),给出\(q\)个询问,每个询问给定一个数\(a\),问其是否能表示成若干个牛逼数的和.
思路
这个题也是乍看上去非常难搞,因为很难用什么性质快速确定一个数是不是一个牛逼的数,而且一个牛逼的数的组合还很多,很复杂,他可以在前面加数也可以在后面加数,基本是很难搞的.
既然去找一个数是不是牛逼数很困难,不妨就直接拿\(d\)去拼出\(a\).设\(q = a % d\),那么当\(q=0\)时显然可以表示成若干个\(d\)之和,否则需要考虑能否把这个\(q\)给凑成是一个牛逼的数:这里的形式就是把\(a\)拆成了\(q,d,d,d,d,d....d\).现在想要知道的问题就是对于\(q\)来说,他加上几个\(d\)才能是一个牛逼的数,由于\(q,d\leq 9\),所以这件事情可以直接暴力预处理出来,\(jup[i][j]\)表示\(i\)这个数加上最少几个\(d\)才能是一个牛逼的数.剩下的就是看这个\(a\)里面包含了几个\(d\),当\(d\)的数量足够\(q\)去拼成一个牛逼的数的时候,答案为YES
.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
int jup[17][17];
int main()
{
forn(i,1,9)
{
forn(d,1,9)
{
int sum = i;
while(1)
{
sum += d;
int x = sum,ok = 0;
while(x)
{
if(x % 10 == d) ok = 1;
x /= 10;
}
++jup[i][d];
if(ok == 1) break;
}
}
}
// cout << jup[4][7] << endl;
int T;scanf("%d",&T);
while(T--)
{
int q,d;scanf("%d%d",&q,&d);
forn(i,1,q)
{
int a;scanf("%d",&a);
int l = a % d,have = a / d;
if(jup[l][d] <= have) puts("YES");
else puts("NO");
}
}
return 0;
}
C. Nezzar and Symmetric Array
题目大意:定义一个牛逼的数组,它的长度是\(2*n\),且每个元素互不相同,并且对任意一个\(a_i\)来说,这个数组里面都存在一个\(-a_i\).同时定义一个数组里面每个元素的牛逼值:\(d_i = \sum\limits_{j=1}^{2*n} |a_i - a_j|\),也就是每个元素和其他所有元素差的绝对值之和.现在只知道所有元素的牛逼值\(d\),问你构造一个满足这个牛逼值的牛逼数组\(a\).不需要输出具体形态,只需要YES
或者NO
.
要求\(a_i\neq 0\).
思路
虽然这个题目一看不需要输出具体形态,很有可能有什么直接判断的解,不过在这个题也没什么用.
由于这个牛逼数组他的定义是一个镜像的形态,我们不妨把正数部分抠出来,也就是把正数的前\(n\)项按从小到大的顺序排列,那么如果能构造出来这个正数部分,负数部分既然就是前面的相反数,也就是搞一个\(0 < a_1 <a_2 < a_3 < ... < a_n\)出来,为了方便,\(a_{i+n}=-a_i\).那么对于任意一个\(d_i\)可以把表达式里的绝对值通过大小关系直接删去,最后可以搞出一个式子:\(d_i = 2 * i * a_i + 2 * S_n - 2*S_i\),式中\(S_i\)表示\(a_i\)的前缀和,这个表达式可以做一个递推:首先\(a_n\)的值可以直接算出来,其次\(d_{i+1} - d_i = 2 * i * (a_{i+1} - a_i)\),那么在知道\(a_{i+1}\)的前提下,可以推出他的前一位\(a_i\).那么剩下的就是从\(a_n\)往前递推就可以了,需要判断中途是否出现了违法情况.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
const int N = 2e5+7;
ll d[N],a[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
n = 2 * n;
map<ll,int> cnt;
forn(i,1,n) scanf("%lld",&d[i]),cnt[d[i]] ++;
int ok = 1;
forn(i,1,n) if(cnt[d[i]] != 2)
{
puts("NO");
ok = 0;
break;
}
if(!ok) continue;
sort(d + 1,d + n + 1);
vector<ll> n_d;n_d.push_back(0ll);
for(int i = 1;i <= n;i += 2) n_d.push_back(d[i]);
n /= 2;
forn(i,1,n) d[i] = n_d[i];
if(d[n] % (2 * n))
{
puts("NO");
continue;
}
cnt.clear();
a[n] = d[n] / (2 * n);cnt[a[n]] ++;
forr(i,1,n - 1)
{
ll up = d[i + 1] - d[i],dw = 2 * i;
if(!dw || up % dw)
{
ok = 0;
puts("NO");
break;
}
a[i] = a[i + 1] - up / dw;
if(a[i] <= 0 || cnt.count(a[i]))
{
ok = 0;
puts("NO");
break;
}
cnt[a[i]] ++;
}
if(ok) puts("YES");
}
return 0;
}
D. Nezzar and Board
题目大意:有\(n\)个互不相同的数\(x\),每次可以从所有数里面选出两个数\(x,y\),加入一个新的数\(2*x-y\),注意不会这个操作不会把原来的\(x,y\)两个元素删掉.问是否能得到目标值\(k\).
思路
如果把这个操作\(2 * x - y\)看做是$x + x - y \(,那么就可以把整个过程看作是任取一个元素\)x\(并且加上任意两个数的差,这个差可以带正数符号也可以带负数符号.其次可以注意到像\)x_3-x_1\(这样的差可以被\)x_3-x_2\(和\)x_2-x_1\(这样的差表示出来,也就是说只需要考虑形如\)x_i - x_{i-1}\(这样的差,构造一个数组\)a\(:\)a_i = x_i - x_{i-1}\((删去\)x_1\().那么现在的问题就是任取\)x_i\(中的某个元素,让他加上\)k_1a_1 + k_2 * a_2....k_na_n\(这样的式子,问他是否能得到目标值.先不管\)x_i\(,考虑后面这坨表达式能凑出什么东西:\)n\(个项的裴蜀定理,他能表示的值一定是\)gcd(a_1,a_2,...a_n)\(的倍数(就是两项的拓展).那么现在问题就是,记\)g\(是整个数组\)a\(的最大公约数,问题就是是否存在某个\)x_i\(有\)x_i+z * g == k\(式中\)z\(是任意整数.那么直接对每个\)x_i$检查就可以了.
对于这个题来说,还有一个点,其实只用检查\(x_1\)就可以了,因为对于其他的情况,可以加上某个\(a_i\)来代替.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 2e5+7;
ll x[N];
ll gcd(ll x,ll y)
{
if(y == 0) return x;
return gcd(y,x % y);
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
ll n,k;scanf("%lld%lld",&n,&k);
forn(i,1,n) scanf("%lld",&x[i]);
sort(x + 1,x + n + 1);
ll g = x[2] - x[1];
forn(i,3,n) g = gcd(g,x[i] - x[i - 1]);
if((k - x[1]) % g == 0) puts("YES");
else puts("NO");
}
return 0;
}
E. Nezzar and Binary String
题目大意:一开始有一个二进制串\(s\),一共有\(q\)次询问,每个询问给定一个区间\([l,r]\),以及两个过程:首先看这个区间里面是否同时含有\(0,1\),如果有则会立即停止过程,如果都是一样的,则可以修改这个区间里面严格小于区间长度一半的元素.问经过\(q\)次询问,且不能中途退出的前提下,是否能把一开始的\(s\)变成另外一个二进制串\(f\).
思路
正着做很难搞,考虑反着做:
所有的操作逆过来之后,就等于是在考虑先修改不超过区间长度一半的元素,之后要求修改之后的整个区间同色.做完了所有询问之后,看整个串是不是和目标串相同.那么修改什么呢?因为要保证整个区间同色,所以其实所有修改操作全部都是把较少的颜色直接修改成较多的颜色,不存在说要去判断跟目标串的距离.那么一个区间推平,且能区间查询元素个数的线段树就可以了.
对于中途退出的判断,只会在整个区间长度是偶数,并且两个颜色的个数相同的时候引发,此时直接输出NO
即可.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
typedef pair<int,int> pii;
#define x first
#define y second
const int N = 2e5+7;
struct Node
{
int l,r;
int v,lazy;
}tr[N * 4];
pii inv[N];
char SST[N],EED[N];
void pushup(int u)
{
tr[u].v = tr[u << 1].v + tr[u << 1 | 1].v;
}
void pushdown(int u)
{
auto& s = tr[u],&lf = tr[u << 1],&rt = tr[u << 1 | 1];
if(~s.lazy)
{
lf.v = (lf.r - lf.l + 1) * s.lazy;
lf.lazy = s.lazy;
rt.v = (rt.r - rt.l + 1) * s.lazy;
rt.lazy = s.lazy;
s.lazy = -1;
}
}
void build(int u,int l,int r)
{
if(l == r) tr[u] = {l,r,EED[l] - '0',-1};
else
{
int mid = l + r >> 1;
tr[u] = {l,r,0,-1};
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
int query(int u,int l,int r)
{
if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;
int mid = tr[u].l + tr[u].r >> 1,res= 0;
pushdown(u);
if(l <= mid) res += query(u << 1,l,r);
if(r > mid) res += query(u << 1 | 1,l,r);
return res;
}
void modify(int u,int l,int r,int v)
{
if(tr[u].l >= l && tr[u].r <= r)
{
tr[u].v = v * (tr[u].r - tr[u].l + 1);
tr[u].lazy = v;
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if(l <= mid) modify(u << 1,l,r,v);
if(r > mid) modify(u << 1 | 1,l,r,v);
pushup(u);
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n,q;scanf("%d%d",&n,&q);
scanf("%s%s",SST + 1,EED + 1);
forn(i,1,q) scanf("%d%d",&inv[i].x,&inv[i].y);
build(1,1,n);
int ok = 1;
forr(i,1,q)
{
int c1 = query(1,inv[i].x,inv[i].y),c0 = inv[i].y - inv[i].x + 1 - c1;
if(c0 == c1)
{
ok = 0;
break;
}
modify(1,inv[i].x,inv[i].y,c0 < c1);
}
forn(i,1,n) if(query(1,i,i) != SST[i] - '0') {ok = 0;break;}
if(ok) puts("YES");
else puts("NO");
}
return 0;
}
F. Nezzar and Nice Beatmap
题目大意:平面上有\(n\)个互不相同的点,每三个连续的点会产生一个角,要求重排给定的点集合的顺序,使每个角都是锐角.
对于第一个元素和最后一个元素不构成角的情况忽略.
思路
假如说有个钝角三角形,那么钝角对应的一定是最大的那一条边.现在把三条边去掉一条,留下两条构成一个角,如果构成的是钝角,那么肯定是把较短的两个选上了,为了避免选出一个钝角,直觉就是从某个点出发,每次链接的点都是当前点距离最远的点.
假如说我们拿任意一个点作为起点,并且拿他去找另外一个最远点,再把这个两个点的连线作为半径画一个圆,形成的圆可以包含所有的点,并且从起点连向的另外一个点,再去连任何一个点都不会形成一个钝角:一旦形成钝角,根据三角形两边之和的规律,与这个点是最远点矛盾了.那么每次都按最大值取,就可以保证每次都是锐角了.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
typedef pair<int,int> pii;
#define x first
#define y second
const int N = 5005;
pii a[N];
bool st[N];
ll dist(const pii& a,const pii& b)
{
ll dx = a.x - b.x,dy = a.y - b.y;
return dx * dx + dy * dy;
}
int main()
{
int n;scanf("%d",&n);
forn(i,1,n) scanf("%d%d",&a[i].x,&a[i].y);
st[1] = 1;printf("1 ");
int c = 1;
forn(i,2,n)
{
ll maxd = -1e18;
int t = -1;
forn(j,1,n)
{
if(st[j]) continue;
ll d = dist(a[c],a[j]);
if(d > maxd)
{
maxd = d;
t = j;
}
}
if(t == -1) break;
st[t] = 1;
c = t;
printf("%d ",t);
}
return 0;
}