codeforces round 919 dv2 C Partitioning the Array
大致题意,对于n约数i,我们把原数组分成份,并且每份中有i个元素,对于每个分组情况,如果存在一个数m使得数组中元素modm后使得每个部分的数组完全相同,如果存在那么ans ++求ans
这是一道数论题,数论题的关键在于将数学问题用数学公式表示出来,现在考虑如果存在一个数组是否存在一个整数m使得数组amodm后所有元素相同
对于这个简化的问题首先列出数学公式 对于,
有
对这个同余式子移项后可得到,对于这个式子我们得到这样一个结论
是可以整除
的。当然m不能为1,现在把这个式子拓展到整个数组,那么就有了所有相邻元素相减的绝对值都应该整除m,并且m不为1,换句话说这些数两两不互质。当然m的最大值就是这些数的gcd了(codeforces round991 div3 F Maximum modulo equality)。
现在再回到这个问题上,这个问题就相当于这个m必须同时满足很多个数组(个,每个子数组中的元素)每个数组求出gcd中,再将这些gcd求总共gcd,只要gcd不为1,就说明ans可以++。当然这里也涉及到时间复杂度的计算,首先的约数预处理
最外层的for是约数个数,数据范围内最大的约数个数为168个,总之约数的个数都很小。紧接着的两重循环不大会算,但是感觉可以过:(
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int gcd(int a, int b) {
return b? gcd(b, a % b): a;
}
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for(int i = 1; i <= n; i ++ ) cin >> a[i];
vector<int> divi;
for(int i = 1; i <= n / i; i ++ ) {
if(n % i == 0) {
divi.push_back(i);
if(n / i != i) divi.push_back(n / i);
}
}
int ans = 1;
for(auto t: divi) {//168
bool ok = true;
if(t == n) continue;
else {
vector<int> alls;
for(int i = 1; i <= t; i ++ ) {
int _gcd = -1;
for(int j = i + t; j <= n; j += i ) {
if(_gcd == -1) _gcd = abs(a[j] - a[j - t]);
else _gcd = gcd(_gcd, abs(a[j] - a[j - t]));
}
alls.push_back(_gcd);
}
int _gcd = -1;
for(auto t: alls) {
if(_gcd == -1) _gcd = t;
else _gcd = gcd(t, _gcd);
}
if(_gcd == 1) ok = false;
}
if(ok) ans ++;
}
cout << ans << "\n";
}
int main() {
ll t;
cin >> t;
while(t -- ) solve();
return 0;
}
codeforces round914div2 C array game
这道题是个tips题,注意到当k大于3时答案总是0,在遇到无从下手的题目时一定要试着去寻找一些特判情况,将答案简化
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 1e18;
void solve() {
ll n, k;
cin >> n >> k;
vector<ll> a(n + 1);
ll minv = INF;
for(int i = 1; i <= n; i ++ ) {
cin >> a[i];
minv = min(minv, a[i]);
}
sort(a.begin() + 1, a.end());
ll ans = INF;
if(k > 2) {
cout << "0\n";
return;
} else if(k == 1) {
for(int i = 1; i <= n; i ++ ) {
for(int j = 1; j <= n; j ++ ) {
if(i == j) continue;
ans = min(ans, abs(a[i] - a[j]));
}
}
} else if(k == 2) {
for(int i = 1; i <= n; i ++ ) {
for(int j = i + 1; j <= n; j ++ ) {
if(i == j) continue;
ll del = a[j] - a[i];
int l = 1, r = n;
while(l < r) {
int mid = l + r >> 1;
if(a[mid] >= del) r = mid;//找到第一个大于等于del的数
else l = mid + 1;
}
ans = min(ans, abs(a[l] - del));
ans = min(ans, abs(a[l - 1] - del));
}
}
}
cout << min(ans, minv) << "\n";
}
int main() {
ll t;
cin >> t;
while(t -- ) solve();
return 0;
}
D set to max
题目大意:给出两个数组,a,b,每次可以从a中任意选择一段区间,并且把区间中元素赋值成区间最大值,easy version和hard version的区别就在于数据范围,有经验的话会立刻明白这个数据范围的作用就是限制求区间最值时的方式,时间复杂度允许的话可以直接暴力求解,反之则可以用线段树维护。现在去思考如何解决这个问题:为了方便我们对数据进行处理,把目标区间取出来是必要的。现在考虑方案的可行性,下述区间的表述都是取出来的区间段,即对于一个区间段的任意
,满足
。如果a中该区间段最大值大于了该区间中b的值(设为
),那么直接判负,因为较大数值会覆盖较小的数值.。同样的如果想要赋值成x,就要保证a数组中存在x,并且在达到x之前没有比x更大的数。这样就结束了吗,上文中提到了,较大值会覆盖最小值,倘若在枚举到当前区间前维护的区间的x比该区间的x要大,并且恰好我这个区间所需要的值被覆盖掉了,那么会导致可行的方案被判否,因此要贪心的以x值较小的区间开始维护,并且在以后的维护中,如果在a中的x必须跨越先前已经维护过的区间,那么答案就会被判否(因为会将以前维护好的覆盖掉)
现在思路就明朗了,时间复杂度整体可控
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
void solve() {
int n;
cin >> n;
vector<int> a(n + 2), b(n + 1);
for(int i = 1; i <= n; i ++ ) cin >> a[i];
for(int i = 1; i <= n; i ++ ) cin >> b[i];
map<int, int> ma;
a[0] = a[n + 1] = INF;
vector<int> L(n + 1), R(n + 1);
for(int i = 1; i <= n; i ++ ) {
if(ma[b[i]] == 0) L[i] = 0;
else L[i] = ma[b[i]];
ma[a[i]] = i;
}
ma.clear();
for(int i = n; i >= 1; i -- ) {
if(ma[b[i]] == 0) R[i] = n + 1;
else R[i] = ma[b[i]];
ma[a[i]] = i;
}
struct node {
int l, r, val, tag;
bool operator < (const node &W) {
return val < W.val;
}
};
vector<node> tr(n * 4 + 1);
auto pushup = [&](int u) {
tr[u].val = max(tr[u << 1].val, tr[u << 1 | 1].val);
};
function<void(int, int, int)> build = [&](int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if(l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
};
function<void(int, int)> insert = [&](int u, int pos) {
if(tr[u].l == tr[u].r && tr[u].l == pos) tr[u].val = a[pos];
else {
int mid = tr[u].l + tr[u].r >> 1;
if(mid >= pos) insert(u << 1, pos);
if(mid < pos) insert(u << 1 | 1, pos);
pushup(u);
}
};
function<int(int, int, int)> query = [&](int u, int l, int r) {
if(tr[u].l >= l && tr[u].r <= r) return tr[u].val;
else {
int res = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(mid >= l) res = query(u << 1, l, r);
if(mid < r) res = max(res, query(u << 1 | 1, l, r));
return res;
}
};
build(1, 1, n);
for(int i = 1; i <= n; i ++ ) insert(1, i);
vector<node> st(4 * n + 1);
function<void(int, int, int)> build1 = [&](int u, int l, int r) {
st[u].l = l, st[u].r = r;
if(l == r) return;
int mid = l + r >> 1;
build1(u << 1, l, mid);
build1(u << 1 | 1, mid + 1, r);
};
auto pushup1 = [&](int u) {
st[u].val = st[u << 1].val | st[u << 1 | 1].val;
};
auto pushdown = [&](int u) {
st[u << 1].val |= st[u].tag;
st[u << 1].tag |= st[u].tag;
st[u << 1 | 1].val |= st[u].tag;
st[u << 1 | 1].tag |= st[u].tag;
st[u].tag = 0;
};
function<void(int, int, int)> insert1 = [&](int u, int l, int r) {
if(st[u].l >= l && st[u].r <= r) {
st[u].val = 1;
st[u].tag = 1;
} else {
pushdown(u);
int mid = st[u].l + st[u].r >> 1;
if(mid >= l) insert1(u << 1, l, r);
if(mid < r) insert1(u << 1 | 1, l, r);
pushup1(u);
}
};
function<int(int, int, int)> query1 = [&](int u, int l, int r) {
if(st[u].l >= l && st[u].r <= r) return st[u].val;
else {
int res = 0;
int mid = st[u].l + st[u].r >> 1;
if(mid >= l) res = query1(u << 1, l, r);
if(mid < r) res |= query1(u << 1 | 1, l, r);
return res;
}
};
build1(1, 1, n);
vector<node> pos;
int i = 1, j = 1;
while(i <= n && j <= n) {
while(j <= n && b[i] == b[j]) j ++;
pos.push_back({i, j - 1, b[i]});
i = j;
}
//cout << L[4] << " ";
sort(pos.begin(), pos.end());
for(int k = 0; k < pos.size(); k ++ ) {
auto t = pos[k];
int l = t.l, r = t.r, x = t.val;
//cout << l << " " << r << " " << x << "\n";
int max_val = query(1, l, r);
if(max_val > x) {
cout << "NO\n";
return;
}
if(max_val == x) {
insert1(1, l, r);
continue;
}
bool ok1 = false;
bool ok2 = false;
if(a[L[l]] == x && !query1(1, L[l], l) && (query(1, L[l], l) == x)) ok1 = true;
if(a[R[r]] == x && !query1(1, r, R[r]) && (query(1, r, R[r]) == x)) ok2 = true;
if(!ok1 && !ok2) {
cout << "NO\n";
return;
} else {
/*for(int i = l; i <= r; i ++ ) insert1(1, i);*/
insert1(1, l, r);
}
}
//cout << query1(1, 1, 2) << " ";
cout << "YES\n";
}
int main() {
int t;
cin >> t;
while(t -- ) solve();
return 0;
}
codeforces round 932 div2 C find a mine
这是一道交互题,这道题这道题启示我到一个点的曼哈顿距离相等的所有点的路径是个菱形(如果没有超过图的边界的话)
D1. XOR Break — Solo Version
题目大意:对于x我们可以将x设为y或者xXORy并且满足并且
问是否可以将x变成m
数据范围很大,并且有xor操作,直接考虑拆位
现在似乎仍然无从下手,那么试着挖掘一些隐含在题目中的性质
m小于x试着考虑一下这个条件的性质:
容易得到,如果xy满足小于关系那么有
x: same 1 ??????
m:same 0 ??????
从高位起第一位不同的二进制表示,x为1,m为0
再考虑如果x Xor y < x要满足什么条件
x: same 1 ??????
y : 00000 1 ??????
m : same 0 ??????(x xor y = m)
其实这里可以注意到如果same存在1的话,那么后面的????填什么都可以因为y肯定比x小并且m本来就比x小,现在要讨论的情况就是same全为0的情况。如果m的?号位为0的话,那么x和y的?号位要相同,不是0就是1,反之?号位不同,也就是说,如果在same 1以后的二进制表示位中,如果m中出现的第一个1对应的x中二进制中表示位是1的话,那么我们同样可以将x转化为m,反之就证明当前的y比x要小,不满足题意,这时候我们要考虑是否可以借助一个中间值转化一下,使得这一位的值变成1(借位),借位只能向1借,并且不能same后面的那位1实际上是不能动的,这里可以手动模拟一下,也就是说,如果在same1后面的二进制位到该位之间x二进制表示位为1的话,我们就可以借助这个中间值将其转化为上述的情况。(可以找个较小的数值模拟一下)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
ll n, m;
cin >> n >> m;
bool ok = 0;
ll pos = -1;
for(ll i = 62; i >= 0; i -- ) {
if(n >> i & 1) {
if(!(m >> i & 1)) {
pos = i;
break;
}
}
if(n >> i & 1) ok = true;
}
//cout << pos << "LL";
if(ok) {
cout << "1\n";
cout << n << " " << m << "\n";
} else {
bool flag = false;
for(ll i = pos - 1; i >= 0; i -- ) {
if(m >> i & 1) {
if(n >> i & 1) {
flag = true;
break;
}
if(!(n >> i & 1)) break;
}
}
if(flag) {
cout << "1\n";
cout << n << " " << m << "\n";
} else {/*
0 0 0 0 0 1 a b c 0
0 0 0 0 0 1 a b c 1
0 0 0 0 0 0 0 0 0 1
*/
ll pos1 = -1;
for(ll i = pos - 1; i >= 0; i -- ) {
if(m >> i & 1) break;
if(n >> i & 1) {
pos1 = i;
break;
}
}
if(pos1 == -1) cout << "-1\n";
else {
cout << "2\n";
ll res = n;
ll now = 1ll << pos;
for(ll i = 62; i >= pos; i -- ) {
res -= (n >> i & 1) * (1ll << i);
}
for(ll i = pos1 - 1; i >= 0; i -- ) {
if((m >> i & 1) && !(n >> i & 1)) {
res += 1ll << i;
break;
}
}
cout << n << " " << res << " " << m << "\n";
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
ll t;
cin >> t;
while(t -- ) solve();
return 0;
}
codeforces round 991 div3 G
题目大意,从一颗树中删除一条链,找到删除后联通块数量的最大值
树类的问题,直接考虑dfs,树形dp
首先有一个很重要的性质,路径一定要尽可能多的删(除了叶子节点),并且每个点的贡献与这个点的临边数量有关,其实思考一下就是树的直径,只不过把边权换成了点权...
这里说一下树的直径:
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
int n;
cin >> n;
vector<vector<int>> adj(n + 1);
for(int i = 2; i <= n; i ++ ) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
vector<int> dp(n + 1);
int ans = 0;
function<void(int, int)> dfs = [&](int u, int fa) {
int tot = adj[u].size();
dp[u] = max(0, tot - 2);
int fir = -1, sec = -1;
bool flag = true;
for(auto t: adj[u]) {
if(t == fa) continue;
flag = false;
dfs(t, u);
dp[u] = max(dp[u], tot - 2 + dp[t]);
if(dp[t] >= fir) {
sec = fir;
fir = dp[t];
} else if(dp[t] >= sec) {
sec = dp[t];
}
int res = max(0, fir) + max(0, sec);
if(tot == 1) res ++;
else if(tot > 2) res += tot - 2;
ans = max(res, ans);
}
if(flag) dp[u] = 1;
};
if(n == 2) cout << "1\n";
else {
dfs(1, 1);
cout << ans << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while(t -- ) solve();
return 0;
}
codeforces round 993 div4 F easy demon problem
问题:
题目大意,给定数组a, b并且让a,b分别作为一个矩阵的长和宽,并且矩阵位置(i, j)的值为a[i] * b[j],给出q个询问,每个询问读入一个x,问是否可以删去一行一列使得剩下的点的和为x,如果不存在q个询问的话对a排序,二分b找答案即可,但是现在又存在q个询问,因此问题变得棘手,应该做出某些预处理。注意到这道题的值域很小,给出的x的绝对值也不超过2e5,因此考虑从值域下手。对于任意的i,j,最后的结果可以用如下式子表示优化过后可以表示成两个式子乘积的形式
(其实可以直接表示成这种形式的,第一种形式并不能引导我们得到正确答案,因为我们已知值域,只有乘积的形式才可以优化时间复杂度)注意到x的绝对值是小于200000的,因此对于两个数的乘积不超过200000,暴力枚举即可,时间复杂度
这个时间复杂度不太会证明,好像和调和级数有关系....对于负数的情况,把最大值设成偏移量即可
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 200010;
void solve() {
int n, m, q;
cin >> n >> m >> q;
vector<ll> a(n + 1), b(m + 1);
for(int i = 1; i <= n; i ++ ) cin >> a[i];
for(int j = 1; j <= m; j ++ ) cin >> b[j];
ll suma = 0, sumb = 0;
for(int i = 1; i <= n; i ++ ) suma += a[i];
for(int i = 1; i <= m; i ++ ) sumb += b[i];
vector<ll> resa(2 * N + 1), resb(2 * N + 1), res(2 * N + 1);
for(int i = 1; i <= n; i ++ ) {
if(abs(suma - a[i]) <= 200000) {
resa[suma - a[i] + N] = 1;
}
}
for(int i = 1; i <= m; i ++ ) {
if(abs(sumb - b[i]) <= 200000) {
resb[sumb - b[i] + N] = 1;
}
}
if(resa[N] || resb[N]) res[N] = 1;
for(ll i = 1; i <= 200000; i ++ ) {
for(ll j = 1; i * j <= 200000; j ++ ) {
res[i * j + N] |= ((resa[i + N] && resb[j + N]) || (resa[N - i] && resb[N - j]));
res[N - i * j] |= ((resa[N - i] && resb[j + N]) || (resa[i + N] && resb[N - j]));
}
}
while(q -- ) {
int x;
cin >> x;
if(res[x + N]) cout << "YES\n";
else cout << "NO\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
t = 1;
while(t -- ) solve();
return 0;
}
codeforces round 995 div3 D counting pairs
题目大意:从一个序列中删掉两个数,使得剩下的数的和大于x小于y,显然二分,但是这道题加深了我对二分的理解:
如图所示的具有二分性质的一个区间,位置2,3分别表示最后一个满足条件的位置与第一个不满足条件的位置,这两个位置是可以二分求解的,但是位置1与位置4是不可以用第一个满足的条件的位置与最后一个不满足条件的位置这样的思路用二分处理的,当然也没有这个必要,特判即可,但是一旦用二分处理就会出错。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
ll n, x ,y;
cin >> n >> x >> y;
vector<ll> a(n + 1);
ll s = 0;
for(ll i = 1; i <= n; i ++ ) {
cin >> a[i];
s += a[i];
}
ll ans = 0;
sort(a.begin() + 1, a.end());
//ll j = n;//表示第一个now - a[j] >= x的位置
for(ll i = 1; i <= n - 1; i ++ ) {
ll now = s - a[i];
if(now - a[i + 1] < x) continue;
ll l = i + 1, r = n;
while(l < r) {
ll mid = l + r + 1 >> 1;
if(now - a[mid] >= x) l = mid;
else r = mid - 1;
}
if(now - a[l] >= x) {
ll tmp = l;
l = i + 1, r = n;
if(now - a[n] <= y) {
ll rr = min(tmp, n);
l = i + 1, r = n;
while(l < r) {
ll mid = l + r >> 1;
if(now - a[mid] <= y) r = mid;
else l = mid + 1;
}
if(now - a[l] <= y) {
ll ll = max(i + 1, l);
ans += rr - ll + 1;
//cout << i << " " << ll << " " << rr << "\n";
}
}
}
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
ll t;
cin >> t;
while(t -- ) solve();
return 0;
}
E: Best price
问题:
先翻译一下题目,将题目转化为数学问题。设price为x,对于每一对a b,当且仅当时,商家会收到差评,求解差评不超过k时的最大收益。可以这样建模,以i为x轴以价格为y轴,对于每一个i都会产生一条在y轴上的
的线段,做一条平行于x轴的水平线,这条水平线不能同时穿过k条这样的线段。如果在不考虑值域范围的情况下,只需要对每条线段做出差分,最后扫一遍值域,小于等于k的位置对应的price就都是合法解,这个合法解再乘上所有可以支付的起这个价格的顾客(
)的数量,取max就是答案。但是现在值域很大,注意到只有a[i],b[i]会成为可能的价格,因此要对原值域做出离散化操作,赛时直接否定了这总做法,是因为atcoder上一道离散化的题,当是想法也是对区间进行离散化,,但是之后在查询操作时发现这样会出现错误,实际上在那道题中,查询所给出的值可能不在离散化数组中,因此会出现错误,但是这道题所有的查询位置一定会在我们的离散化数组中,这样是不会出现问题的。至于顾客数量,二分即可
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
ll n, k;
cin >> n >> k;
vector<int> a(n + 1), b(n + 1);
for(int i = 1; i <= n; i ++ ) cin >> a[i];
for(int i = 1; i <= n; i ++ ) cin >> b[i];
vector<int> alls;
for(int i = 1; i <= n; i ++ ) {
alls.push_back(a[i]);
alls.push_back(a[i] + 1);
alls.push_back(b[i]);
alls.push_back(b[i] + 1);
}
sort(alls.begin(), alls.end());
auto find = [&](int x) {
int l = 0, r = alls.size() - 1;
while(l < r) {
int mid = l + r + 1 >> 1;
if(alls[mid] <= x) l = mid;
else r = mid - 1;
}
return l;
};
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
vector<ll> diff(alls.size() + 1);
for(int i = 1; i <= n; i ++ ) {
diff[find(a[i] + 1)] += 1;
diff[find(b[i] + 1)] -= 1;
}
for(int i = 0; i < alls.size(); i ++ ) if(i != 0) diff[i] += diff[i - 1];
ll ans = 0;
sort(b.begin() + 1, b.end());
for(int i = 0; i < alls.size(); i ++ ) {
if(diff[i] <= k) {
ll cons = alls[i];//树的价格
int l = 1, r = n;
while(l < r) {
int mid = l + r >> 1;
if(b[mid] >= cons) r = mid;
else l = mid + 1;
}
if(b[l] >= cons) {
ans = max(ans, cons * (n - l + 1));
}
}
}
cout << ans << "\n";
}
int main() {
int t;
cin >> t;
while(t -- ) solve();
return 0;
}