2022NOIP A层联测26
A. 乘筛积
\(exgcd\) 不能乱改
直接 \(exgcd\) 优化找 \(x\)+记忆化能过,但是因为脑残加了个自认为的剪枝于是挂惨了
题解做法是根号分治
exgcd
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
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;
}
int exgcd(int a, int b, int &x, int &y){
if(!b){x = 1; y = 0; return a;}
int gcd = exgcd(b, a % b, y, x);
y -= a / b * x;
return gcd;
}
const int maxn = 300005;
const int mod = 998244353;
int n, m, c;
int a[maxn], b[maxn];
typedef pair<int, int> pii;
map<pair<int, int>, int>mp;
void sol(){
int p = read(), q = read();
if(mp[pii(p, q)]){
printf("%d\n",mp[pii(p, q)]);
return;
}
int x, y;
int gcd = exgcd(p, q, x, y);
if(c % gcd){
printf("%d\n",0);
return;
}
int ans = 0;
int mq = q / gcd;
x = (1ll * x * c / gcd) % mq;
x = (x + mq) % mq;
for(int i = x; i <= n; i += mq){
int j = c - i * p;
if(j < q)break;
if(j % q)continue;
j /= q;
if(j > m)continue;
ans = (ans + 1ll * a[i] * b[j] % mod) % mod;
}
mp[pii(p, q)] = ans;
printf("%d\n",ans);
}
int main(){
freopen("sedge.in","r",stdin);
freopen("sedge.out","w",stdout);
n = read(), m = read(), c = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= m; ++i)b[i] = read();
int t = read();
for(int i = 1; i <= t; ++i)sol();
return 0;
}
根号分治
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
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 = 300005;
const int mod = 998244353;
int n, m, c, mx;
int a[maxn], b[maxn];
typedef pair<int, int> pii;
map<pair<int, int>, int>mp;
void sol(){
int p = read(), q = read();
if(c % __gcd(p, q)){printf("0\n");return;}
if(mp[pii(p, q)]){printf("%d\n",mp[pii(p, q)]);return;}
int ans = 0;
if(p > q){
for(int i = max(1ll, (c - 1ll * m * q) / p); i <= n; ++i){
int j = c - i * p; if(j < q)break;
if(j % q)continue; j /= q;
if(j <= m) ans = (ans + 1ll * a[i] * b[j] % mod) % mod;
}
}else{
for(int i = max(1ll, (c - 1ll * n * p) / q); i <= m; ++i){
int j = c - i * q; if(j < p)break;
if(j % p)continue; j /= p;
if(j <= n) ans = (ans + 1ll * a[j] * b[i] % mod) % mod;
}
}
mp[pii(p, q)] = ans;
printf("%d\n",ans);
}
int main(){
// freopen("sedge.in","r",stdin);
// freopen("sedge.out","w",stdout);
n = read(), m = read(), c = read();
mx = sqrt(c);
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= m; ++i)b[i] = read();
int t = read();
for(int i = 1; i <= t; ++i)sol();
return 0;
}
B. 放进去
一堆假贪心过的。。。
\(SOS\) \(DP\) 即子集和 \(DP\)
求解形如 \(f_i = \sum_{i | j = i} g_j\)
本质上与 \(fwt\) 相同, 也可以看做高维前缀和
其实是优化了暴力, \(f_s\) 表示选择集合为 \(s\) 的最小代价,直接转移复杂度很高,考虑优化
对每个物品分别考虑
选择一个集合,贡献是其中对应最小的 \(a\),集合状态该位不为 \(0\),那么贡献都是 \(a\), 如果为 \(0\) 则有新的贡献
我们用 \(SOS\) 进行子集上的差分,具体来讲按照 \(a\) 降序处理,设当前的前缀状态集合为 \(s\), 那么\(f_s += a_i\)
不含新加入的这一位的贡献不变于是 \(f_{las} -= a_i\)
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
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 = 34000000;
int n, m, a[30];
ll sb[maxn], f[maxn];
int lg[maxn];
int p[30], b[30];
bool cmp(int x, int y){return a[x] < a[y];}
int main(){
freopen("putin.in","r",stdin);
freopen("putin.out","w",stdout);
n = read(), m = read();
for(int i = 1; i <= m; ++i)p[i] = i - 1;
for(int i = 1; i <= n; ++i){
for(int j = 0; j < m; ++j)a[j] = read();
sort(p + 1, p + m + 1, cmp);
for(int s = 0, j = m; j > 0; --j){
f[s] -= a[p[j]]; s |= (1 << p[j]); f[s] += a[p[j]];
}
}
for(int i = 0; i < m; ++i)
for(int j = 0; j < (1 << m); j += (1 << (i + 1)))
for(int k = 0; k < (1 << i); ++k)
f[j + k] += f[j + k + (1 << i)];
for(int i = 1; i <= m; ++i)b[i] = read();
for(int i = 2; i < (1 << m); i += i)lg[i] = lg[i >> 1] + 1;
for(int i = 1; i < (1 << m); ++i)sb[i] = sb[i ^ (i & -i)] + b[lg[i & -i] + 1];
ll ans = 4e18;
for(int s = 1; s < (1 << m); ++s)ans = min(ans, sb[s] + f[s]);
printf("%lld\n",ans);
return 0;
}
C. 最长路径
考虑一个点能由谁转移过来,转移的点满足 \(x_i + 1 == x _j\) 或者 \(y_i + 1 == y_j\) 否则一定不是最优
暴力枚举每个包含当前点的矩形,找到能贡献的最小 \(x, y\)进行转移
然后优化这个过程,首先优化找 \(x, y\)
考虑一个矩形会贡献到的是一个矩形范围,我们在矩形下边界处放最小值,最后取后缀 \(min\)
按照最小值排序,这样一个点只会覆盖一次于是可以并查集优化找点过程
而对应的转移优化,则是发现后缀 \(min\) 有单调性,于是用单调队列优化
为了方便查方案数,每个单调队列配套一个桶,记录 \(f = i\)时队列中元素的方案和
目前 \(TLEcoders\)上 \(TLE\) 70,有大佬会卡常吗
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
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 = 2055;
const int mod = 1e9 + 7;
int n, m, q;
struct matrix{
int r1, c1, r2, c2;
}d[520005];
struct DSU{
int f[maxn];
void init(int x){for(int i = 1; i <= x; ++i)f[i] = i;}
int fa(int x){return f[x] == x ? x : f[x] = fa(f[x]);}
void merge(int x, int y){x = fa(x); y = fa(y); if(x < y)swap(x, y); f[y] = x;}
}s[maxn];
int p[520005];
int f[maxn][maxn], g[maxn][maxn];
void add(int &x, int y){x += y; x = x >= mod ? x - mod : x;}
void Ckmi(int &x, int y){x = x > y ? y : x;}
struct Qr{
int q[maxn], head, tail, i, sg[maxn];
void init(int _i){head = 1; tail = 0; i = _i;}
void push(int v){
while(head <= tail && f[i][q[tail]] < f[i][v]){
add(sg[f[i][q[tail]]], mod - g[i][q[tail]]);
--tail;
}
q[++tail] = v; add(sg[f[i][v]], g[i][v]);
}
void pop(int lim){
while(head <= tail && q[head] < lim){
add(sg[f[i][q[head]]], mod - g[i][q[head]]);
++head;
}
}
int qf(int lim){
pop(lim);
if(head <= tail)return f[i][q[head]];
else return -0x3f;
}
int qg(){return sg[f[i][q[head]]];}
void clear(){pop(0x3f3f3f3f);}
}qr[maxn];
struct Qu{
int q[maxn], head, tail, i, sg[maxn];
void init(int _i){head = 1; tail = 0; i = _i;}
void push(int v){
while(head <= tail && f[q[tail]][i] < f[v][i]){
add(sg[f[q[tail]][i]], mod - g[q[tail]][i]);
--tail;
}
q[++tail] = v; add(sg[f[v][i]], g[v][i]);
}
void pop(int lim){
while(head <= tail && q[head] < lim){
add(sg[f[q[head]][i]], mod - g[q[head]][i]);
++head;
}
}
int qf(int lim){
pop(lim);
if(head <= tail)return f[q[head]][i];
else return -0x3f;
}
int qg(){return sg[f[q[head]][i]];}
void clear(){pop(0x3f3f3f3f);}
}qu[maxn];
int mil[maxn][maxn], miu[maxn][maxn];
bool cmp1(int x, int y){return d[x].r1 < d[y].r1;}
bool cmp2(int x, int y){return d[x].c1 < d[y].c1;}
void sol(){
n = read(), m = read(), q = read();
for(int i = 1; i <= q; ++i)d[i].r1 = read(), d[i].c1 = read(), d[i].r2 = read(), d[i].c2 = read();
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
f[i][j] = g[i][j] = 1;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
mil[i][j] = j, miu[i][j] = i;
for(int i = 1; i <= q; ++i)p[i] = i; sort(p + 1, p + q + 1, cmp1);
for(int i = 1; i <= n; ++i)s[i].init(m);
for(int i = 1; i <= q; ++i){
int now = p[i], R = d[now].r2;
if(d[now].r2 > d[now].r1){
for(int j = d[now].c1 + 1; j <= d[now].c2; j = s[R].fa(j) + 1)Ckmi(miu[d[now].r2][j], d[now].r1);
for(int j = d[now].c1 + 1; j < d[now].c2; j = s[R].fa(j))s[R].merge(j, j + 1);
}
}
sort(p + 1, p + q + 1, cmp2);
for(int i = 1; i <= m; ++i)s[i].init(n);
for(int i = 1; i <= q; ++i){
int now = p[i], R = d[now].c2;
if(d[now].c2 > d[now].c1){
for(int j = d[now].r1 + 1; j <= d[now].r2; j = s[R].fa(j) + 1)Ckmi(mil[j][d[now].c2], d[now].c1);
for(int j = d[now].r1 + 1; j < d[now].r2; j = s[R].fa(j))s[R].merge(j, j + 1);
}
}
for(int i = 1; i <= n; ++i)
for(int j = m - 1; j >= 1; --j)Ckmi(mil[i][j], mil[i][j + 1]);
for(int i = 1; i <= m; ++i)
for(int j = n - 1; j >= 1; --j)Ckmi(miu[j][i], miu[j + 1][i]);
int ans1 = 0, ans2 = 0;
for(int i = 1; i <= m; ++i)qu[i].init(i - 1);
for(int i = 1; i <= n; ++i){
qr[i].init(i - 1);
for(int j = 1; j <= m; ++j){
int l = mil[i][j];
if(i > 1){
int qf = qr[i].qf(l);
if(qf + 1 > f[i][j]){
f[i][j] = qf + 1;
g[i][j] = qr[i].qg();
}else if(qf + 1 == f[i][j])add(g[i][j], qr[i].qg());
qr[i].push(j);
}
int u = miu[i][j];
if(j > 1){
int qf = qu[j].qf(u);
if(qf + 1 > f[i][j]){
f[i][j] = qf + 1;
g[i][j] = qu[j].qg();
}else if(qf + 1 == f[i][j])add(g[i][j], qu[j].qg());
qu[j].push(i);
}
if(l < j && u < i && f[i - 1][j - 1] + 1 == f[i][j])add(g[i][j], mod - g[i - 1][j - 1]);
if(f[i][j] > ans1)ans1 = f[i][j], ans2 = g[i][j];
else if(f[i][j] == ans1)add(ans2, g[i][j]);
}
qr[i].clear();
}
for(int i = 1; i <= m; ++i)qu[i].clear();
printf("%d %d\n",ans1, ans2);
}
int main(){
freopen("path.in","r",stdin);
freopen("path.out","w", stdout);
int t = read();
for(int i = 1; i <= t; ++i)sol();
return 0;
}
D. 生成树的传说
先考虑判定问题,可以贪心处理,枚举当前值 , 把 \(l\) 小于等于当前值的 \(r\) 加入堆,每次匹配 \(r\) 最小的
考虑处理最小生成树的限制,我们实际上是有很多形如 \(w_i > w_j\) 的限制
那么令 \(l_i = max(l_i, l_j + 1) r_j = min(r_j, r_i - 1)\)这样 \(j\) 比 \(i\)先入堆先出堆,一定符合题意
现在考虑构造字典序最小的方案,枚举答案 \(k\)
根据 \(hall\) 定理, \(k\) 合法当且仅当不存在 \(L <= k <= R ,R - L + 1 <= \sum[L <= l_i <= r_i <= R]\)
对式子移项, \(R <= L - 1 + \sum[]\) 于是枚举 \(R\), 线段树维护右边的,查询最大值,然后线段树二分到最左侧不合法的 \(L\),把这一段标记成不能选,最后选能选的最小数即可
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
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 = 3505;
void ckmi(int &x, int y){x = x < y ? x : y;}
void ckmx(int &x, int y){x = x > y ? x : y;}
int n, m;
struct node{int u, v, l, r;}d[maxn];
int head[maxn], tot;
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 fa[maxn], dep[maxn], id[maxn];
void pre(int x){
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v == fa[x])continue;
fa[v] = x; dep[v] = dep[x] + 1; id[v] = (i + 1) >> 1;
pre(v);
}
}
void upd(int x){
int u = d[x].u, v = d[x].v;
while(u != v){
if(dep[u] < dep[v])swap(u, v);
ckmi(d[id[u]].r, d[x].r - 1);
ckmx(d[x].l, d[id[u]].l + 1);
u = fa[u];
}
}
int pl[maxn];
priority_queue<int, vector<int>, greater<int>>q;
bool cmp(int x, int y){return d[x].l < d[y].l;}
bool check(){
for(int i = 1; i <= m; ++i)pl[i] = i;
sort(pl + 1, pl + m + 1, cmp);
int p = 1;
for(int i = 1; i <= m; ++i){
while(p <= m && d[pl[p]].l <= i)q.push(d[pl[p]].r), ++p;
if(q.empty())return false;
if(q.top() < i)return false;
q.pop();
}
return true;
}
struct seg{
struct node{
int val, tag;
}t[maxn << 2 | 1];
void upd(int x, int val){t[x].val += val; t[x].tag += val;}
void push_up(int x){t[x].val = max(t[x << 1].val, t[x << 1 | 1].val);}
void push_down(int x){upd(x << 1, t[x].tag); upd(x << 1 | 1, t[x].tag); t[x].tag = 0;}
void modify(int x, int l, int r, int L, int R, int val){
if(L <= l && r <= R)return upd(x, val);
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);
}
void built(int x, int l, int r){
t[x].tag = 0;
if(l == r){
t[x].val = l - 1;
return;
}
int mid = (l + r) >> 1;
built(x << 1, l, mid);
built(x << 1 | 1, mid + 1, r);
push_up(x);
}
int query(int x, int l, int r, int val){
if(l == r){
if(t[x].val >= val)return l;
else return -1;
}
if(t[x].tag)push_down(x);
int mid = (l + r) >> 1;
if(t[x << 1].val >= val)return query(x << 1, l, mid, val);
else return query(x << 1 | 1, mid + 1, r, val);
}
}t;
int val[maxn], cf[maxn];
bool vis[maxn];
vector<int>v[maxn];
void sol(){
for(int i = m; i >= 1; --i)v[d[i].r].push_back(d[i].l);
for(int i = 1; i <= m; ++i){
t.built(1, 1, m);
v[d[i].r].pop_back();
for(int j = 1; j <= m; ++j)cf[j] = 0;
for(int r = 1; r <= m; ++r){
if(vis[r])t.modify(1, 1, m, 1, r, 1);
for(int x : v[r])t.modify(1, 1, m, 1, x, 1);
if(r < d[i].l)continue;
int pos = t.query(1, 1, m, r);
if(pos != -1)++cf[pos], --cf[r + 1];
}
for(int j = 1; j <= m; ++j)cf[j] += cf[j - 1];
for(int j = d[i].l; j <= d[i].r; ++j)if(cf[j] == 0 && !vis[j]){
vis[j] = true; val[i] = j; break;
}
}
for(int i = 1; i <= m; ++i)printf("%d ",val[i]);printf("\n");
}
int main(){
freopen("mst.in","r",stdin);
freopen("mst.out","w",stdout);
n = read(), m = read();
bool flag = true;
for(int i = 1; i <= m; ++i){
d[i].u = read(), d[i].v = read();
d[i].l = read(), d[i].r = read();
if(i < n)add(d[i].u, d[i].v), add(d[i].v, d[i].u);
}
pre(1);
for(int i = n; i <= m; ++i)upd(i);
if(check())sol();
else printf("-1\n");
return 0;
}