模拟赛小结:Gym 102800 The 14th Jilin Provincial Collegiate Programming Contest
比赛链接:传送门
阴差阳错地又开始训练了。
lh考研去了,我和hat又坑到了另一个很强的学弟guapi组队。The 1226465-th prime number 又回来啦。
我和guapi差不多停训了一年,hat倒是一直在训练,前段时间cf还上了橙,Orz。
回来第一场先练了个吉林的省赛热热手(省赛的水题还是挺多的,做得很开心),罚时放到现场赛居然还能拿个亚军。
Problem A. Chord 00:15 (+) Solved by xk (小模拟)
代码:
#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int N = 1e3 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int main()
{
map<string, int> mp;
mp["C"] = 1;
mp["C#"] = 2;
mp["D"] = 3;
mp["D#"] = 4;
mp["E"] = 5;
mp["F"] = 6;
mp["F#"] = 7;
mp["G"] = 8;
mp["G#"] = 9;
mp["A"] = 10;
mp["A#"] = 11;
mp["B"] = 12;
fast;
int T;
cin >> T;
while(T--)
{
string a, b, c;
cin >> a >> b >> c;
int x = mp[a], y = mp[b], z = mp[c];
if(x > y) y += 12;
if(y > z) z += 12;
if(y - x == 4 && z - y == 3) cout << "Major triad\n";
else if(y - x == 3 && z - y == 4) cout << "Minor triad\n";
else cout << "Dissonance\n";
}
}
Problem B. Problem Select 00:06 (+) Solved by Guapi (排序 签到)
代码:
#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int N = 1e3 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int n, k, a[N];
char s[N];
int main()
{
int t;
scanf("%d", &t);
while(t --)
{
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++ i) {
scanf("%s", s + 1);
int len = strlen(s + 1);
int j = len;
while(j > 0 && s[j] != '/') -- j;
int x = 0;
for(j = j + 1; j <= len; ++ j) {
x = x * 10 + s[j] -'0';
}
a[i] = x;
}
sort(a + 1, a + n + 1);
for(int i = 1; i <= k; ++ i) {
printf("%d ", a[i]);
}
puts("");
}
return 0;
}
Problem C. String Game 00:32 (+1) Solved by Dancepted (dp 背包)
题目大意:
给两个字符串s,t,求字符串s有多少个子序列和t完全相同。
解法:
用$cnt_{j}$表示t的长度为j的前缀能有多少个匹配。那么在后面出现$s_{i}=t_{j+1}$时,长度为j+1的前缀就会增加cnt_{j}个匹配。
是一个类似01背包的转移,所以第二个循环应该倒着跑。
代码:$O(nm)$
#include <bits/stdc++.h>
#define sz(x) ((int)x.size())
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
#define N 5005
#define M 1005
using namespace std;
typedef long long ll;
ll cnt[M];
ll md = 1000000007;
int main(){
fast;
string s, t;
while (cin >> s >> t) {
memset(cnt, 0, (sz(t) + 1) * sizeof(ll));
cnt[0] = 1;
for (int i = 0; i < sz(s); i++) {
for (int j = sz(t)-1; j >= 0; j--) {
if (s[i] == t[j]) {
cnt[j+1] = (cnt[j+1] + cnt[j]) % md;
}
}
}
cout << cnt[sz(t)] << endl;
}
return 0;
}
Problem D. Trie (待补)
hat的补题代码(做法大概是AC自动机fail树上用树状数组搞一下差分什么的)
#include<bits/stdc++.h>
#define forn(i, n) for (int i = 0; i < (n); i++)
#define forab(i, a, b) for (int i = (a); i <= (b); i++)
#define forba(i, b, a) for (int i = (b); i >= (a); i--)
#define mset(a, x, n) memset(a, x, sizeof(a[0]) * (n))
#define updiv(x, y) (((x) + (y) - 1) / (y)) // y > 0
#define pb(x) push_back(x)
#define eb emplace_back
#define mp(x, y) make_pair(x, y)
#define fi first
#define se second
#define sz(x) ((int)x.size())
#define all(x) (x).begin(), (x).end()
#define endl '\n'
#ifdef hat
#define fast
#define de(x) cout << #x << '=' << (x) << ' '
#define dee(x) cout << #x << '=' << (x) << endl
#else
#define fast ios::sync_with_stdio(0), cin.tie(0)
#define de(x) ((void) 0)
#define dee(x) ((void) 0)
#endif
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair<int, int> pii;
typedef vector<int> VI;
const int maxn = 1e5 + 5;
const int mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll llINF = 0x3f3f3f3f3f3f3f3f;
ll make_compiler_happy() {return INF & maxn & mod & llINF;}
ll fpow(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll inv(ll x) {return fpow(x, mod-2);}
int ch[maxn][26];
int n;
int f[maxn];
vector<int> g[maxn];
void getfail()
{
queue<int> q;
f[0] = 0;
for(int c = 0; c < 26; c++)
{
int u = ch[0][c];
if(u) {
f[u] = 0;
q.push(u);
}
}
while(!q.empty())
{
int r = q.front(); q.pop();
for(int c = 0; c < 26; c++)
{
int u = ch[r][c];
if(!u) continue;
q.push(u);
int v = f[r];
while(v && !ch[v][c]) v = f[v];
f[u] = ch[v][c];
}
}
for(int i = 1; i < n; i++)
{
g[f[i]].push_back(i);
}
}
int cur;
int in[maxn], out[maxn];
int dep[maxn];
void dfs1(int u, int d)
{
dep[u] = d;
in[u] = ++cur;
for(int v : g[u])
{
dfs1(v, d + 1);
}
out[u] = cur;
}
int sum[maxn * 4];
#define mid (l+r)/2
#define ls o*2
#define rs o*2+1
void add(int o, int l, int r, int ql, int qr)
{
if(ql <= l && r <= qr) {
sum[o]++;
return;
}
if(ql > r || qr < l) {
return;
}
add(ls, l, mid, ql, qr);
add(rs, mid+1, r, ql, qr);
}
int bit[maxn];
void bitadd(int x, int d)
{
for(; x <= cur + 1; x += x & -x) {
bit[x] += d;
}
}
int bitsum(int x)
{
int s = 0;
for(; x; x -= x & -x) {
s += bit[x];
}
return s;
}
void add(int x)
{
add(1, 1, cur, in[x], out[x]);
}
int query(int o, int l, int r, int x, int s)
{
if(l == r) return s + sum[o];
if(x <= mid) return query(ls, l, mid, x, s + sum[o]);
else return query(rs, mid + 1, r, x, s + sum[o]);
}
int main()
{
fast;
int T;
cin >> T;
while(T--)
{
cin >> n;
memset(sum, 0, sizeof(sum));
for(int i = 0; i < n; i++)
{
g[i].clear();
memset(ch[i], 0, sizeof(ch[i]));
}
for(int i = 1; i < n; i++)
{
int u; char c;
cin >> u >> c;
u--;
ch[u][c-'a'] = i;
}
cur = 0;
getfail();
dfs1(0, 0);
int m;
cin >> m;
while(m--)
{
int t;
cin >> t;
if(t == 1) {
int k;
cin >> k;
vector<int> v(k);
for(int i = 0; i < k; i++)
{
cin >> v[i];
v[i]--;
}
sort(v.begin(), v.end(), [&](int x, int y) {return dep[x] < dep[y];});
vector<int> tmp;
for(int i : v)
{
if(bitsum(in[i])) {
continue;
}
add(i);
tmp.push_back(i);
bitadd(in[i], 1);
bitadd(out[i] + 1, -1);
}
for(int i : tmp)
{
bitadd(in[i], -1);
bitadd(out[i] + 1, 1);
}
// forn(i, n)
// {
// cout << query(1, 1, cur, in[i], 0) << ' ';
// }
// cout << endl;
}
else {
int x;
cin >> x;
x--;
cout << query(1, 1, cur, in[x], 0) << endl;
}
}
}
}
Problem E. Shorten the Array 00:51 (+) Solved by Dancepted&hat (思维题)
看题目发呆了好久,后来脑门一拍想了个结论就过了。
题目大意:
给出一个长度为n(n <= 1e6)的数组,每次可以选择两个位置相邻的数a、b(要求a和b都大于0),用a%b或者b%a替换他们。
思路:
设最小的数是mn,数量有cnt个。
如果cnt = 1,显然可以通过(ai, mn)=(mn % ai)= (mn),来删去所有的点,答案就是1。
如果cnt ≠ 1,则需要讨论一下。
两个结论:
1、若所有的数都是mn的倍数,则答案为$\left \lceil \frac{cnt}{2} \right \rceil$
2、若存在一个数x不是mn的倍数,设y为与mn相邻的一个数则必定可以通过(mn, y) = (mn % y),把mn换到x的旁边,然后(mn, x)=(x%mn),且x%mn < mn,之后就可以用x%mn消去所有的
代码:
#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int n;
int a[N];
void solveTestCase() {
cin >> n;
int mn = 1e9 + 5, cnt = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (mn > a[i]) {
mn = a[i];
cnt = 1;
}
else if (mn == a[i]) {
cnt++;
}
}
bool isOne = false;
for (int i = 1; i <= n; i++) {
if (a[i] % mn != 0) {
isOne = true;
break;
}
}
if (isOne)
cout << 1 << endl;
else
{
cout << (cnt + 1) / 2 << endl;
}
}
int main() {
fast;
int t;
cin >> t;
while (t--) {
solveTestCase();
}
return 0;
}
Problem F. Queue 04:11 (+5) Solved by Guapi (树状数组+暴力,树套树)
没看到$p_{i2}−p_{i1} ≤ 100$,用树套树写了半天没调出来的样子,后来看到了这个条件就暴力过了。
因为太久做题,出现了一些RE,MLE之类的错误,多了不少罚时。
题目大意:
动态逆序对,每次操作是交换两个数的位置,或询问逆序对的数量。
解法:
树套树经典题,但这个两个数的位置距离不超过100,也可以树状数组+暴力。
比赛代码(树状数组+暴力):
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&(-x))
typedef long long ll;
const int N = 1e5 + 10;
const int M = 2500000;
int a[N], n, m;
int c[N];
void add(int x) {
for(; x <= N - 5; x += lowbit(x)) {
c[x] ++ ;
}
}
int ask(int x) {
int res = 0;
for(; x > 0; x -= lowbit(x)) {
res += c[x];
}
return res;
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
ll cnt = 0;
scanf("%d", &n);
memset(c, 0, sizeof(c));
for(int i = 1; i <= n; ++ i) {
scanf("%d", &a[i]);
++ a[i];
cnt += i - 1 - ask(a[i]);
add(a[i]);
}
ll ans = cnt;
scanf("%d", &m);
int L, R;
while(m --)
{
scanf("%d%d", &L, &R);
if(L == R) continue;
int cnt1 = 0, cnt2 = 0;
for(int j = L + 1; j <= R - 1; ++ j) {
if(a[j] < a[L]) ++ cnt1;
if(a[j] > a[R]) ++ cnt2;
}
cnt = cnt - cnt1 - cnt2;
cnt1 = cnt2 = 0;
for(int j = L + 1; j <= R - 1; ++ j) {
if(a[j] > a[L]) ++ cnt1;
if(a[j] < a[R]) ++ cnt2;
}
cnt = cnt + cnt1 + cnt2;
if(a[L] > a[R]) -- cnt;
swap(a[L], a[R]);
if(a[L] > a[R]) ++ cnt;
ans = min(ans, cnt);
}
printf("%lld\n", ans);
}
return 0;
}
树套树代码:
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&(-x))
typedef long long ll;
const int N = 1e5 + 10;
const int M = 2500000;
int a[N], tot, n, m, mx;
struct tree{
int lc, rc, cnt;
}T[N << 7];
int root[N];//正常树的根节点
int ux[50], uy[50], numx, numy; //保存树状数组的节点
int S[N];//树状数组根节点
void build(int &rt, int l, int r)//建树
{
rt = ++ tot;
T[rt].cnt = 0;
T[rt].lc = T[rt].rc = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(T[rt].lc, l, mid);
build(T[rt].rc, mid + 1, r);
}
void UP(int &cur, int l, int r, int pos, int val)//节省内存
{
if (!cur) cur = ++ tot;
T[cur].cnt += val;
if (l == r) return;
int mid = (l + r) >> 1;
if (pos <= mid) UP(T[cur].lc, l, mid, pos, val);
else UP(T[cur].rc, mid + 1, r, pos, val);
}
void update(int &cur, int pre, int l, int r, int pos, int val)
{
cur = ++tot;//新树必须新开节点
T[cur] = T[pre];
T[cur].cnt += val;
if (l == r) return;
int mid = (l + r) >> 1;
if (pos <= mid) update(T[cur].lc, T[pre].lc, l, mid, pos, val);
else update(T[cur].rc, T[pre].rc, mid + 1, r, pos, val);
}
void clear(int &cur, int l, int r)
{
T[cur].cnt = 0;
if(!cur || l == r) return ;
int mid = (l + r) >> 1;
if(T[cur].lc) clear(T[cur].lc, l, mid);
if(T[cur].rc) clear(T[cur].rc, mid + 1, r);
cur = 0;
}
void add(int pos, int val)//用树状数组更新
{
int x = a[pos];
for (; pos <= n; pos += lowbit(pos))
UP(S[pos], 0, mx, x, val);
}
int query(int l, int r, int L, int R, int x, int y) {
if(x > y) return 0;
if (x <= l && r <= y)
{
int ans = T[R].cnt - T[L].cnt;
for (int i = 0; i < numy; i++) ans += T[uy[i]].cnt;
for (int i = 0; i < numx; i++) ans -= T[ux[i]].cnt;
return ans;
}
int Ux[50], Uy[50];
for (int i = 0; i < numy; i++) Uy[i] = uy[i];
for (int i = 0; i < numx; i++) Ux[i] = ux[i];
int mid = (l + r) >> 1;
int sum = 0;
if (x <= mid)
{
for (int i = 0; i < numy; i++) uy[i] = T[Uy[i]].lc;
for (int i = 0; i < numx; i++) ux[i] = T[Ux[i]].lc;
sum = query(l, mid, T[L].lc, T[R].lc, x, y);
}
if (y > mid)
{
for (int i = 0; i < numy; i++) uy[i] = T[Uy[i]].rc;
for (int i = 0; i < numx; i++) ux[i] = T[Ux[i]].rc;
sum += query(mid + 1, r, T[L].rc, T[R].rc, x, y);
}
return sum;
}
int Q(int L)
{
int cnt = 0;
// left, large than a[L]
if(L > 1) {
numx = 0, numy = 0;
for (int j = 0; j > 0; j -= lowbit(j)) ux[numx++] = S[j];
for (int j = L - 1; j > 0; j -= lowbit(j)) uy[numy++] = S[j];
cnt += query(0, mx, root[0], root[L - 1], a[L] + 1, mx);
}
// right, less than a[L]
if(L < n) {
numx = 0, numy = 0;
for (int j = L; j > 0; j -= lowbit(j)) ux[numx++] = S[j];
for (int j = n; j > 0; j -= lowbit(j)) uy[numy++] = S[j];
cnt += query(0, mx, root[L], root[n], 0, a[L] - 1);
}
return cnt;
}
int main()
{
int t;
scanf("%d", &t);
while(t --)
{
tot = 0;
ll cnt = 0;
scanf("%d", &n);
mx = 0;
for(int i = 1; i <= n; ++ i) {
scanf("%d", &a[i]);
mx = max(mx, a[i]);
}
build(root[0], 0, mx);
numx = 0, numy = 0;
for(int i = 1; i <= n; ++ i) {
S[i] = 0;
update(root[i], root[i - 1], 0, mx, a[i], 1);
cnt += query(0, mx, root[0], root[i - 1], a[i] + 1, mx);
}
ll ans = cnt;
int L, R;
scanf("%d", &m);
while(m --)
{
scanf("%d%d", &L, &R);
if(L == R) continue;
int delta = 0;
delta -= Q(L) + Q(R);
if(a[L] > a[R]) delta ++;
add(L, -1);
add(R, -1);
swap(a[L], a[R]);
add(L, 1);
add(R, 1);
delta += Q(L) + Q(R);
if(a[L] > a[R]) delta --;
cnt += delta;
ans = min(ans, cnt);
}
printf("%lld\n", ans);
for(int i = 1; i <= tot; ++ i) {
T[i].cnt = T[i].lc = T[i].rc = 0;
}
}
return 0;
}
Problem G. Matrix 01:18 (+) Solved by Dancepted&hat (数论,思维题)
思路:
翻转次数为奇数的情况下,才对最终的答案有贡献。
(x,y)的翻转次数 = x约数的个数 × y的约数的个数。
设$x = \prod p_{i}^{t_{i}}(p_{i}是质数)$,则约数个数$d(x) = \prod(t_{i}+1)$,所有的$t_{i}$都必须是偶数,才对最终的答案有贡献。
也就是说x和y是完全平方数。$[1, n]$区间内的完全平方数的平方根的范围为$[1, \left \lfloor \sqrt{n} \right \rfloor]$。
解法:
答案就是$\left \lfloor \sqrt{n} \right \rfloor * \left \lfloor \sqrt{m} \right \rfloor$。
代码:
#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
ll sq(ll x)
{
ll t = sqrt(x) - 1;
if(t < 0) t = 0;
while((t + 1) * (t + 1) <= x) t++;
return t;
}
int main() {
fast;
int t;
cin >> t;
while (t--) {
ll n, m;
cin >> n >> m;
cout << sq(n) * sq(m) << endl;
}
return 0;
}
Problem H. Curious 03:43 (+1) Solved by Dancepted&hat (数论 容斥 莫比乌斯函数)
需要使劲分析复杂度的一道数论题。
WA了一发是因为加记忆化的时候,忘记用long long了。
题目大意:
给出一个长度为n的数列a,满足$a_{i}<=m$,k次询问,每次给出一个x,回答:
$\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(a_{i}, a_{j})==x]$
思路:
用$sum_{i}$表示数列中为i的倍数的数的数量。
①对于x = 1的情况:
$\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(a_{i}, a_{j})==1]$
所有1的倍数的gcd至少为1,这些数的数量就是$sum_{1}*sum_{1}$,但这里面要减去gcd为2、3、5...的那些数,但这样6、10、15之类的又被多减了。
实际上$sum_{i}*sum_{i}$的系数正好是莫比乌斯函数$\mu(i)$。
因此对于x = 1,有$ans = \sum_{m}^{i=1}\mu (i)sum_{i}^2$。
预处理sum_{i}时,可以先O(n)地统计[1, m]内每个数的出现次数,然后用埃氏筛在O(mloglogm)的时间内计算sum。
总的时间复杂度是O(n+mloglogm)的。
②同样的,对于一个给定的x,有:
$\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(a_{i}, a_{j})==x] = \sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(\frac{a_{i}}{x}, \frac{a_{j}}{x})==1]$。
把数列中所有x的倍数的数单独拿出来,并除x。再跑一遍①中的算法,就可以得到答案了,令m' = m / x,时间复杂度为$O(n + m'loglogm)。
这样看起来总复杂度似乎高达$O(n*n + n*mloglogm))$。
复杂度右边的部分实际上是$\sum^{m}_{x=1}\frac{m}{x}loglog(m/x) <=mlogm\sum^{m}_{x=1}\frac{1}{x}$,而$\frac{1}{x}$的上界是一个常数(百度了一下好像是$\frac{\pi^2}{6}$)
并且,数列中的每个数,只会被①调用$d(a_{i})$次($d(n)$表示n的约数的个数),而d(n)的一个显然的上界是$2\sqrt{n}$,复杂度的左边实际上是$O(n\sqrt{n})$。
对于重复的x,也可以记忆化。
代码:$O(n\sqrt{n}+mloglogm))$
#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int a[N];
vector<int> V[N];
int prime[N], miu[N];
int cnt[N], sum[N];
ll solve(vector<int> &v) {
int n = SZ(v);
int m = 0;
for(int i = 0; i < n; i++) {
m = max(m, v[i]);
}
memset(cnt, 0, sizeof(int) * (m + 1));
for(int i = 0; i < n; i++)
cnt[v[i]]++;
memset(sum, 0, sizeof(int) * (m + 1));
// ll ans = (ll) n * n;
ll ans = 0;
for(int i = 1; i <= m; i++)
{
for(int j = i; j <= m; j += i) {
sum[i] += cnt[j];
}
ans += (ll) miu[i] * sum[i] * sum[i];
}
return ans;
}
void solveTestCase() {
int n, m, k;
cin >> n >> m >> k;
for(int i = 1; i <= m; i++) {
V[i].clear();
cnt[i] = 0;
}
for(int i = 1; i <= n; i++) {
cin >> a[i];
cnt[a[i]]++;
}
for(int i = 1; i <= m; i++) {
for(int j = i; j <= m; j+=i) {
for(int _ = 0; _ < cnt[j]; _++) {
V[i].push_back(j / i);
}
}
}
map<int, ll> mp;
for(int i = 0; i < k; i++) {
int x;
cin >> x;
if(x > m || x <= 0) cout << 0 << endl;
else {
if(mp.count(x)) {
cout << mp[x] << endl;
}
else {
ll t = solve(V[x]);
mp[x] = t;
cout << t << endl;
}
}
}
}
void getPrime_and_Miu() {
miu[1] = 1;
for (int i = 2; i <= N; i++) {
if (!prime[i]) prime[++prime[0]] = i, miu[i] = -1;
for (int j = 1; j <= prime[0] && prime[j] <= N/i; j++) {
prime[i*prime[j]] = 1;
miu[i*prime[j]] = miu[i] * (i%prime[j] ? -1 : 0);
if (i%prime[j] == 0) break;
}
}
}
int main() {
fast;
getPrime_and_Miu();
int t; cin >> t;
while (t--) {
solveTestCase();
}
return 0;
}
Problem I. World Tree 04:05 (+) Solved by Dancepted (经典贪心 + 树形dp)
思路:
题目中有说到,如果当前节点存在孩子,就不能返回到父节点,换句话说就是在树上dfs。
所以如果有策略可以确定dfs的顺序,那么这题就解决了。
令$A_{u} = \sum_{v\in以u为根的子树}a_{v}$,$B_{u} = \sum_{v\in以u为根的子树}b_{v}$
以节点$u$为根的子树,对答案的贡献可以分成两类:
1、子树的根$u$与子树内所有其他节点之间产生的贡献,这部分是无法改变的,为$a_{u}*(B_{u} - b_{u})$;
2、以$u$为根的子树,与以$u$的兄弟节点$u'$及其子树产生的贡献,有两种可能的情况:
①$A_{u}B_{u'}$,此时是先遍历u子树,再遍历u'子树;
②$A_{u'}B_{u}$,此时是先遍历u'子树,再遍历u子树;
如果①>②,说明先遍历u子树更优,反之亦然。
在dfs到下一层之前,根据这个规则对子节点排序后再遍历就可以了。
这个贪心策略之前在CF上做过类似的题。
代码:O(nlogn)
#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int n;
int a[N], b[N];
ll sa[N], sb[N];
vector<int> es[N];
ll ans = 0;
void dfsInit(int u, int fa) {
sa[u] = a[u];
sb[u] = b[u];
for (int v : es[u]) if (v != fa) {
dfsInit(v, u);
sa[u] += sa[v];
sb[u] += sb[v];
}
}
bool cmp(int l, int r) {
return sa[l]*sb[r] > sa[r]*sb[l];
}
void dfsSolve(int u, int fa) {
ans += (ll)a[u]*(sb[u] - b[u]);
sort(es[u].begin(), es[u].end(), cmp);
ll tmp = sb[u] - b[u];
for (int v : es[u]) if (v != fa) {
dfsSolve(v, u);
tmp -= sb[v];
ans += sa[v] * tmp;
}
}
void solveTestCase() {
cin >> n;
for (int i = 1; i <= n; i++) es[i].clear();
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n-1; i++) {
int u, v; cin >> u >> v;
es[u].push_back(v);
es[v].push_back(u);
}
dfsInit(1, -1);
ans = 0;
dfsSolve(1, -1);
cout << ans << endl;
}
int main() {
fast;
int t; cin >> t;
while (t--) {
solveTestCase();
}
return 0;
}
Problem J. Situation 01:55 (+1) Solved by hat(min-max)
少开了一个状态,WA了一发。
思路:
总状态数是$3^{9}*2\approx 4*10^{4}$。可以dfs加剪枝,dfs中用min-max决策。
代码:
#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int N = 1e3 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
struct state{
int a[3][3];
int toint()
{
int p3 = 1;
int res = 0;
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 3; j++)
{
res += a[i][j] * p3;
p3 *= 3;
}
}
return res;
}
bool isend()
{
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 3; j++)
{
if(!a[i][j]) return 0;
}
}
return 1;
}
int cal()
{
int t = 0;
for(int i = 0; i < 3; i++)
{
int f = a[i][0];
for(int j = 1; j < 3; j++)
{
if(a[i][j] != f) f = 0;
}
if(f) {
if(f == 1) t++;
else t--;
}
}
for(int i = 0; i < 3; i++)
{
int f = a[0][i];
for(int j = 1; j < 3; j++)
{
if(a[j][i] != f) f = 0;
}
if(f) {
if(f == 1) t++;
else t--;
}
}
int f = a[0][0];
for(int j = 1; j < 3; j++)
{
if(a[j][j] != f) f = 0;
}
if(f) {
if(f == 1) t++;
else t--;
}
f = a[0][2];
for(int j = 1; j < 3; j++)
{
if(a[j][2-j] != f) f = 0;
}
if(f) {
if(f == 1) t++;
else t--;
}
return t;
}
};
int res[30000][2];
int dfs(state s, int ismax)
{
int x = s.toint();
if(res[x][ismax] != -1000) {
return res[x][ismax];
}
if(s.isend()) {
return res[x][ismax] = s.cal();
}
if(ismax)
{ // O:1
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
if(s.a[i][j] == 0)
{
state t = s;
t.a[i][j] = 1;
res[x][ismax] = max(dfs(t, !ismax), res[x][ismax]);
}
}
else
{ // X:2
res[x][ismax] = 1000;
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
if(s.a[i][j] == 0)
{
state t = s;
t.a[i][j] = 2;
res[x][ismax] = min(dfs(t, !ismax), res[x][ismax]);
}
}
return res[x][ismax];
}
int main() {
for(int i = 0; i < 30000; i++)
res[i][0] = res[i][1] = -1000;
int t;
cin >> t;
while (t--) {
int ismax;
cin >> ismax;
state t;
for(int i = 0; i < 3; i++)
{
string s;
cin >> s;
for(int j = 0; j < 3; j++){
if(s[j] == '.') t.a[i][j] = 0;
else if(s[j] == 'O') t.a[i][j] = 1;
else t.a[i][j] = 2;
}
}
cout << dfs(t, ismax) << endl;
}
return 0;
}
Problem L. Swimmer 00:30 (+1) Solved by Guapi (签到)
RE了一发是因为数组开小了。。。
代码:
#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> P;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int n, m, q, a[N];
int main()
{
scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= n; ++ i) {
scanf("%d", &a[i]);
}
int p, k;
for(int i = 1; i <= q; ++ i) {
scanf("%d%d", &p, &k);
int y = (1LL * p * a[k] ) % (2 * m);
if(y <= m) printf("%d\n", y);
else printf("%d\n", 2 * m - y);
}
return 0;
}
Problem M. Upanishad 04:38 (+) Solved by Guapi (树状数组)
知道是热身赛的题,就没急着做。
思路:
区间出现次数为偶数的异或和 = 区间出现过的数的异或和 xor 区间出现次数为奇数的异或和。
出现次数为奇数的异或和比较简单,用异或和的前缀和算一算就可以了。(出现次数为偶数的异或和为0)
区间出现过的数,可以这么求(好像是树状数组的一个trick?):遍历的时候如果一个数之前出现过,那么就把树状数组中,在之前出现的位置减去这个数,再在新的位置加上这个数。
代码:O(nlogn)
#include <bits/stdc++.h> using namespace std; #define lowbit(x) (x&(-x)) typedef long long ll; const int N = 5e5 + 10; const int M = 2500000; int a[N], sum[N], n, q, ans[N]; int c[N]; map<int, int> mp; struct node { int l, r, pos; bool operator<(const node &o)const { return r < o.r; } }; node Q[N]; void add(int x, int val) { for(; x <= n; x += lowbit(x)) { c[x] ^= val ; } } int ask(int x) { int res = 0; for(; x > 0; x -= lowbit(x)) { res ^= c[x]; } return res; } int main() { scanf("%d%d", &n, &q); for(int i = 1; i <= n; ++ i) { scanf("%d", &a[i]); sum[i] = a[i] ^ sum[i - 1]; } for(int i = 1; i <= q; ++ i) { scanf("%d%d", &Q[i].l, &Q[i].r); Q[i].pos = i; } sort(Q + 1, Q + q + 1); int L = 1; for(int i = 1; i <= q; ++ i) { while(L <= Q[i].r) { if(mp.count(a[L])){ add(mp[a[L]], a[L]); } mp[a[L]] = L; add(L, a[L]); ++ L; } int res = sum[Q[i].r] ^ sum[Q[i].l - 1]; int res1 = ask(Q[i].r) ^ ask(Q[i].l - 1); res ^= res1; ans[Q[i].pos] = res; } for(int i = 1; i <= q; ++ i) { printf("%d\n", ans[i]); } return 0; }
总结:
省赛还是偏轻松愉快的2333,不过刚回来还是会犯一些低级错误,还有个trick因为太久没做题了,WA了一发才想起来。
C(+1)的解法实现起来的时候,如果正向转移的话,同一层会自己影响自己,这是背包问题里比较常见的trick了,模拟赛中写的时候没有意识到这个问题。
F(+5)暴露出的问题是读题不够仔细+对板子不过熟悉。首先是没有读到完整的题意,实际上做题的时候,如果有特别地说明区间跨度不超过100之类的,肯定是会针对这个条件来设计解法的。其次是题意读了个大概,然后发现和树套树的板子很像,就抛开题目直接上板子了。虽然实际上树套树确实可做,但因为数据量比较特殊,有更简单的解法,可以省下不少机时。
G(+)虽然是一发过的,但是思路上绕了个大弯。我们队对各种数论筛法还是不够熟悉,上来有点念念不忘数论筛法,误以为这是个筛子题,想着怎么用筛子做。过了很久才发现这就是个结论题。
H(+1)代码写的没问题,交之前顺手加了个记忆化,结果记忆化的部分没有用long long。
J(+1)如果是我写的话,我可能也会犯这样的错误吧。动手写中期题的时候,还是要把思路想清楚再摸键盘。边写边想还是很容易漏掉一些点的。
L(+1)数组开太小了。。
还有就是我们讨论的时候容易想到什么就直接说,但我觉得这样讨论的效率其实是比较低的。我觉得应该先独立思考,有什么关键的进展,或者想到了什么解法,再和队友讨论。