2022NOIP A层联测30
A. 分配
都推成和根的关系,然后取 \(lcm\), 用什么东西维护一下质因子的幂次
好像大家都是从上往下推,但是我从下往上推,用\(map\)维护了一下,进行了启发式合并
复杂度好像是 \(nlog^2\)
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 200005;
const int mod = 998244353;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
int n;
int prime[maxn], cnt, mi[maxn];
bool flag[maxn];
int inv[maxn];
void init(){
for(int i = 1; i <= 200000; ++i)inv[i] = qpow(i, mod - 2);
for(int i = 2; i <= 200000; ++i){
if(!flag[i])prime[++cnt] = i, mi[i] = i;
for(int j = 1; j <= cnt && prime[j] * i <= 200000; ++j){
flag[i * prime[j]] = 1; mi[i * prime[j]] = prime[j];
if(i % prime[j] == 0)break;
}
}
}
int head[maxn], tot;
struct edge{int to, net, a, b;}e[maxn << 1 | 1];
void ckmx(int &x, int y){if(x < y)x = y;}
void add(int u, int v, int a, int b){
e[++tot] = {v, head[u], a, b};
head[u] = tot;
}
map<int, int> mp[maxn];
void merge(map<int, int> &x, map<int, int> &y){
if(x.size() < y.size())swap(x, y);
for(auto v : y)ckmx(x[v.first], v.second);
y.clear();
}
void del(map<int, int> &x, int y){
while(y > 1){
if(x[mi[y]] > 0){
--x[mi[y]];
if(x[mi[y]] == 0)x.erase(x[mi[y]]);
}
y /= mi[y];
}
}
void add(map<int, int> &x, int y){
while(y > 1){
++x[mi[y]];
y /= mi[y];
}
}
void dfs(int x, int fa){
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v != fa){
dfs(v, x);
del(mp[v], e[i].b);
add(mp[v], e[i].a);
merge(mp[x], mp[v]);
}
}
}
int val[maxn];
void sol(int x, int fa){
mp[x].clear();
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v != fa){
val[v] = 1ll * val[x] * e[i].b % mod * inv[e[i].a] % mod;
sol(v, x);
}
}
}
void sol(){
n = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read(), a = read(), b = read();
int g = __gcd(a, b); a /= g; b /= g;
add(u, v, a, b); add(v, u, b, a);
}
dfs(1, 0);
for(int i = 1; i <= n; ++i)val[i] = 1;
for(auto v : mp[1]){
val[1] = 1ll * val[1] * qpow(v.first, v.second) % mod;
}
sol(1, 0);
int ans = 0;
for(int i = 1; i <= n; ++i)ans = (ans + val[i]) % mod;
printf("%d\n",ans);
for(int i = 1; i <= n; ++i)head[i] = 0;
tot = 0;
}
int main(){
freopen("arrange.in","r",stdin);
freopen("arrange.out","w",stdout);
init();
int t = read();
for(int i = 1; i <= t; ++i)sol();
return 0;
}
B. 串串超人
比较套路,但是我想了半年
线段树维护叶节点到 \(r\) 的信息
每次移动 \(r\), 当前连续 \(1\) 直接计算贡献,前面的在线段树上查询
因为后缀\(max\) 所以有单调性,每次线段树二分找一段区间覆盖成当前值
扫完这个连续段再回去把每个后缀的贡献单点修到线段树里
不过仔细思考一下发现这东西能用类似单调盏的方法优化到线性
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 500005;
char c[maxn];
int pre[maxn];
int n;
struct seg{
struct node{
ll sum;
int len, tag, mi;
}t[maxn << 2 | 1];
void upd(int x, int val){
t[x].mi = val;
t[x].tag = val;
t[x].sum = 1ll * t[x].len * val;
}
void push_down(int x){upd(x << 1, t[x].tag); upd(x << 1 | 1, t[x].tag); t[x].tag = 0;}
void push_up(int x){t[x].mi = min(t[x << 1].mi, t[x << 1 | 1].mi); t[x].sum = t[x << 1].sum + t[x << 1 | 1].sum;}
void built(int x, int l, int r){
t[x].len = r - l + 1;
if(l == r)return;
int mid = (l + r) >> 1;
built(x << 1, l, mid);
built(x << 1 | 1, mid + 1, r);
}
void modify(int x, int l, int r, int L, int R, int val){
if(L <= l && r <= R){upd(x, val); return;}
if(t[x].tag)push_down(x);
int mid = (l + r) >> 1;
if(L <= mid) modify(x << 1, l, mid, L, R, val);
if(R > mid) modify(x << 1 | 1, mid + 1, r, L, R, val);
push_up(x);
}
int bound(int x, int l, int r, int val){
if(l == r)return l + (t[x].mi >= val);
if(t[x].tag)push_down(x);
int mid = (l + r) >> 1;
if(t[x << 1].mi < val)return bound(x << 1, l, mid, val);
return bound(x << 1 | 1, mid + 1, r, val);
}
ll query(int x, int l, int r, int L, int R){
if(L <= l && r <= R)return t[x].sum;
if(t[x].tag)push_down(x);
int mid = (l + r) >> 1; ll ans = 0;
if(L <= mid)ans += query(x << 1, l, mid, L, R);
if(R > mid)ans += query(x << 1 | 1, mid + 1, r, L, R);
return ans;
}
}t;
ll calc(ll x){return 1ll * (1 + x) * x / 2;}
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
n = read();
scanf("%s",c + 1);
for(int i = 1, las = 0; i <= n; ++i){
if(c[i] == '0')las = i;
pre[i] = las;
}
t.built(1, 1, n);
ll ans = 0;
for(int r = 1; r <= n; ++r){
if(pre[r] == r)ans += t.query(1, 1, n, 1, r);
else{
ans += calc(r - pre[r]);
int pos = t.bound(1, 1, n, r - pre[r]);
if(pos <= pre[r])t.modify(1, 1, n, pos, pre[r], r - pre[r]);
if(pre[r])ans += t.query(1, 1, n, 1, pre[r]);
if(pre[r + 1] == r + 1){
for(int j = pre[r] + 1; j <= r; ++j)t.modify(1, 1, n, j, j, r - j + 1);
}
}
}
printf("%lld\n",ans);
return 0;
}
C. 多米诺游戏
考虑一个数,两次框他的必然不同,把框看成边,那么大概跟图有关
猜测是找环?
打开样例,把框画出来,发现可以拉成 \(2 \times n\) 的长条, 而每条边加两次保证了点的度数为偶数,那么直接欧拉回路即可
不过需要保证同一个框代表的边经过的时刻奇偶性不同,但是因为加边方式,好像我直接跑就是对的
是否有人能证明这样直接跑的正确性?
严谨的解法大概可以参考 \(APJ\) 巨佬,(如果他写日总结)
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 600005;
int n;
int head[maxn], tot = 1;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
}
int deg[maxn];
int d[maxn][2];
bool check(){
for(int i = 1; i <= n; ++i){
if(deg[d[i][0]] == deg[d[i][1]] && deg[d[i][0]] == 2)return false;
}
return true;
}
bool del[maxn << 1 | 1];
vector<int>h[maxn];
int cnt;
void dfs(int x, int id){
for(int &i = head[x]; i; i = e[i].net)if(!del[i]){
int v = e[i].to; --deg[x]; --deg[v]; del[i] = del[i ^ 1] = true;
dfs(v, id);
}
h[id].push_back(x);
}
void sol(){
printf("%d %d\n",2, n);
for(int i = 1; i <= n + n; ++i){
if(deg[i])dfs(i, ++cnt);
}
for(int i = 1; i <= cnt; ++i)h[i].pop_back();
for(int i = 1; i <= cnt; ++i){
int s = h[i].size(); s >>= 1;
for(int j = 0; j < s; ++j)printf("%d ",h[i][j]);
}
printf("\n");
for(int i = 1; i <= cnt; ++i){
int s = h[i].size(); s >>= 1;
for(int j = h[i].size() - 1; j >= s; --j)printf("%d ",h[i][j]);
}
printf("\n");
for(int i = 1; i <= cnt; ++i){
int s = h[i].size(); s >>= 1;
for(int j = 2; j <= s; j += 2)printf("LR");
if(s & 1)printf("U");
}
printf("\n");
for(int i = 1; i <= cnt; ++i){
int s = h[i].size(); s >>= 1;
for(int j = 2; j <= s; j += 2)printf("LR");
if(s & 1)printf("D");
}
printf("\n");
for(int i = 1; i <= cnt; ++i){
int s = h[i].size(); s >>= 1;
printf("U");
for(int j = 3; j <= s; j += 2)printf("LR");
if((s & 1) == 0)printf("U");
}
printf("\n");
for(int i = 1; i <= cnt; ++i){
int s = h[i].size(); s >>= 1;
printf("D");
for(int j = 3; j <= s; j += 2)printf("LR");
if((s & 1) == 0)printf("D");
}
printf("\n");
}
int main(){
//freopen("domino.in","r",stdin);
//freopen("domino.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i){
int u = read(), v = read();
d[i][0] = u; d[i][1] = v;
add(u, v); add(v, u); add(u, v); add(v, u);
++deg[u]; ++deg[u]; ++deg[v]; ++deg[v];
}
if(check())sol();
else printf("-1\n");
return 0;
}
D. 大师
又是一个没见过并且难以想到的 \(trick\)
考虑把奇数位都异或 \(1\), 我们的操作就成了交换相邻两个不同数
考虑一个位置 \(x\) 被经过的次数,是 \(|\sum_{i = 1}^{x} a_i - \sum_{i = 1}^{x} b_i|\)
于是设 \(f_{i, j}\) 表示前 \(i\) 个位置, \(\sum a - \sum b = j\) 的方案数, \(g_{i, j}\)表示操作数,然后直接转移即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 2005;
const int base = 2003;
const int mod = 1e9 + 7;
int n;
char s[maxn], t[maxn];
int a[maxn], b[maxn];
int f[maxn][maxn + maxn], g[maxn][maxn + maxn];
void add(int &x, int y){x += y; if(x >= mod)x -= mod;}
void sol(){
n = read();
scanf("%s%s",s + 1, t + 1);
for(int i = 1; i <= n; ++i)a[i] = s[i] == '?' ? -1 : s[i] - '0';
for(int i = 1; i <= n; ++i)b[i] = t[i] == '?' ? -1 : t[i] - '0';
for(int i = 1; i <= n; i += 2){
if(a[i] != -1)a[i] ^= 1;
if(b[i] != -1)b[i] ^= 1;
}
f[0][base] = 1;
for(int i = 0; i < n; ++i)
for(int j = -i; j <= i; ++j)if(f[i][j + base]){
int la = a[i + 1] == -1 ? 0 : a[i + 1], ra = a[i + 1] == -1 ? 1 : a[i + 1];
int lb = b[i + 1] == -1 ? 0 : b[i + 1], rb = b[i + 1] == -1 ? 1 : b[i + 1];
for(int p = la; p <= ra; ++p)
for(int q = lb; q <= rb; ++q){
add(g[i + 1][j + base + p - q], (g[i][j + base] + 1ll * abs(j + p - q) * f[i][j + base]) % mod);
add(f[i + 1][j + base + p - q], f[i][j + base]);
}
}
printf("%d\n",g[n][base]);
for(int i = 0; i <= n; ++i)
for(int j = -i - 3; j <= i + 3; ++j)f[i][j + base] = g[i][j + base] = 0;
}
int main(){
freopen("master.in","r",stdin);
freopen("master.out","w",stdout);
int t = read(); for(int i = 1; i <= t; ++i)sol();
return 0;
}