2022NOIPA层联测23 今天的所有题都好评!!
信息学考就要到了,于是去补课
我一个OIer花了半节课都没有通过编译……
A. zzy 的金牌
所求即为b1……bn的数量,满足
- b[i]+a[i] >= b[i-1]+a[i-1]
- ∑b = k
关于差分数组和后缀和,放个code我溜了 qwq
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 303;
const int mod = 998244353;
int n, k, a[maxn], ans, f[maxn][maxn][maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
void add(int &x, int y)
{
x += y; x = x >= mod ? x - mod : x;
x = x < 0 ? x + mod : x;
}
int main()
{
freopen("orzzy.in", "r", stdin);
freopen("orzzy.out", "w", stdout);
n = read(); k = read();
for(int i=1; i<=n; i++) a[i] = read();
sort(a+1, a+1+n);
f[0][0][0] = 1; f[0][1][0] = -1;
for(int i=0; i<=n; i++)
{
for(int j=1; j<=k; j++)
{
for(int p=0; p<=k; p++)//前i-1个数的和
{
add(f[i][j][p], f[i][j-1][p]);
}
}
for(int j=0; j<=k; j++)
{
for(int p=0; p<=k-j; p++)
{
int l = max(0,j+a[i]-a[i+1]), r = k-p-j;
if(l > r) continue;
add(f[i+1][l][p+j], f[i][j][p]); add(f[i+1][r+1][p+j], -f[i][j][p]);
}
}
}
for(int i=0; i<=k; i++)
{
add(ans, f[n][i][k-i]);
}
printf("%d\n", ans);
return 0;
}
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 303;
const int mod = 998244353;
int n, k, a[maxn], ans, f[maxn][maxn][maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
void add(int &x, int y)
{
x += y; x = x >= mod ? x - mod : x;
x = x < 0 ? x + mod : x;
}
//我还在鹤……
int main()
{
freopen("orzzy.in", "r", stdin);
freopen("orzzy.out", "w", stdout);
n = read(); k = read();
for(int i=1; i<=n; i++) a[i] = read();
sort(a+1, a+1+n);
f[0][0][k] = 1;
for(int i=0; i<=n; i++)
{
for(int j=0; j<=k; j++)//bi
{
int l = max(a[i]+j-a[i+1], 0);//i+1最少要填l,后面的都可以
for(int r=l; r<=k; r++)//当前的余量从l开始的后缀和,至少要有l的余量才能把i+1填进去
{
if(f[i][j][r]) add(f[i+1][l][r-l], f[i][j][r]);
//现在i是后缀和,而i+1只有当前值
}
}
for(int j=0; j<=k; j++)//bi+1
{
for(int r=1; r<=k; r++)
{
add(f[i+1][j+1][r-1], f[i+1][j][r]);
}
}
}
for(int i=0; i<=k; i++) add(ans, f[n][i][0]);
printf("%d\n", ans);
return 0;
}
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 303;
const int mod = 998244353;
int n, k, a[maxn], ans, f[maxn][maxn][maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
void add(int &x, int y)
{
x += y; x = x >= mod ? x - mod : x;
x = x < 0 ? x + mod : x;
}
//我还在鹤……
//既然都鹤了那么也就不介意再多鹤两版。。
int main()
{
freopen("orzzy.in", "r", stdin);
freopen("orzzy.out", "w", stdout);
n = read(); k = read();
for(int i=1; i<=n; i++) a[i] = read();
sort(a+1, a+1+n);
f[0][0][0] = 1; f[0][1][0] = -1;
for(int i=0; i<=n; i++)
{
for(int s=0; s<=k; s++)
{
for(int j=1; j<=k; j++)
{
add(f[i][j][s], f[i][j-1][s]);
}
for(int j=0; j<=k-s; j++)
{
int l = max(a[i]+j-a[i+1],0), r = k-s-j;
if(l <= r) add(f[i+1][l][s+j], f[i][j][s]), add(f[i+1][r+1][s+j], -f[i][j][s]);
}
}
}
for(int i=0; i<=k; i++) add(ans, f[n][i][k-i]);
printf("%d\n", ans);
return 0;
}
B. 口粮输送
就连树上的判定都不需要dp!!在树上一条边至多被经过一次,也就是一棵∑(bi-ai)>=∑wi的树一定存在合法方案。如果一棵∑(bi-ai)<∑wi的树存在合法方案,则必然存在一条边在这个方案中没有被经过,可以删去这条边。
合法输送方案的导出子图的,每个连通块C一定有∑bi<=∑ai-∑e∈CwC。将C换成它的最小生成树,能解决∑(bi-ai)>=∑e∈CwC的所有问题。E一定是某个最小生成森林。
code
/*
感觉今天的考题适合用来珍藏qwq
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 17;
const int N = 290;
int T, n, m, a[maxn], b[maxn], fa[maxn];
bool f[65539], in[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
struct edge
{
int u, v, w;
bool operator < (const edge &T) const
{
return w < T.w;
}
}e[maxn*maxn];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
bool merge(int x, int y) {x = find(x), y = find(y); if(x == y) return false; fa[y] = x; return true;}
bool mst(int s)
{
int smt = 0, snt = 0, cnt = 0;
for(int i=1; i<=n; i++)
{
if(s & (1<<i-1)) snt += a[i] - b[i], in[i] = true, cnt++;
else in[i] = false;
}
for(int i=1; i<=n; i++) fa[i] = i;
for(int i=1; i<=m; i++)
{
if(in[e[i].u] && in[e[i].v] && merge(e[i].u, e[i].v))
{
smt += e[i].w; cnt--;
}
}
return cnt == 1 && snt - smt >= 0;//这里只考虑树,森林之后再说
}
void solve()
{
n = read(); m = read();
for(int i=1; i<=m; i++)
{
int u = read(), v = read(), w = read();
e[i] = {u, v, w};
}
sort(e+1, e+1+m);
for(int i=1; i<=n; i++) a[i] = read(), b[i] = read();
for(int i=1; i<(1<<n); i++) f[i] = mst(i);
for(int i=1; i<(1<<n); i++) if(f[i] == false)
{
for(int j=(i-1)&i; j&&!f[i]; j=(j-1)&i)
{
f[i] |= f[j] & f[i^j];
}
}
if(f[(1<<n)-1]) printf("Yes\n");
else printf("No\n");
}
int main()
{
freopen("trans.in", "r", stdin);
freopen("trans.out", "w", stdout);
T = read();
while(T--)
{
solve();
}
return 0;
}
C. 作弊
为了防止改不完题,这个神奇的东西我一定要现在就写!
%%%Chen_jr 一看就知道我又鹤了
设s(l, r)表示在[l, r]之间作一次弊的最大收益,这个东西居然可以优化!!转移方程是f[i] = max(f[i], f[j-1]+s(j, i)),发现每一个时刻只有右端点是i的s是有用的,线段树的一部分就是右端点为i时,左端点为(线段树下标)的s的大小,同时还把f[j-1]记录到了f[j]上,这样就可以让答案都在一个点上维护线段树的区间最值了!
现在就需要维护右端点移动时s的变化。(鹤一下)考虑分开计算每个人的贡献,i对s(i, j)产生贡献当且仅当mx(l, r)属于[li, ri],将其拆成max(mx(l, i), mx(i, r))属于[li, ri],也就是mx(l, i),mx(i, r)都要<=ri,且其中至少一个>=li。
对于每个i预处理出mx(l, i),mx(i, r)属于[li, ri]的l, r的范围,记为[lri, lli], [rli, rri](因为mx从i向左递增,从i向右递增),第一个l/r表示是谁的范围,第二个l/r表示满足哪个条件(>=li还是<=ri)。具体实现的时候找的是(以l的范围为例)满足a[j]>=l[i]的i左边的最大的j和满足a[k]>r[i]的i左边的最大的k,它+1才是标准的范围,但是恰好可以在线段树上消去贡献时作为起点。
预处理可以用树状数组,下标是值域,val是位置,因为要找值更大的,可以直接往后找,也可以把树状数组翻转过来让a更大的在左边,向左要找val最小的,向右要找val最大的,原来的函数可以直接用,把权值和答案都变成相反的就好,这样负负得正。
当右端点属于[i, ri]时,左端点在[lli, lri]内有贡献;属于[rli, rri]时,左端点在[lli, i[内有贡献,注意[lri, lli]内的贡献不需要重复统计所以从lli+1开始;大于rri时,没有贡献。第一层循环的i既表示讨论到的位置,又表示当前的右端点,一开始先把i更新为f[i-1]修改之后它就变成i了,f[i-1]不是f[i]的初值因为s(i, i)可能不是0。记录以i为转折点的x,在适当的时候去掉x的贡献。
code
#include <bits/stdc++.h>
using namespace std;
//typedef long long ll;你猜为什么过不了编译
const int maxn = 1e5 + 5;
int f[maxn], a[maxn], n, l[maxn], r[maxn];
int ll[maxn], lr[maxn], rl[maxn], rr[maxn];
vector<int> RL[maxn], RR[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
struct BIT
{
int t[maxn];
int lowbit(int x) {return x & -x;}
void modify(int x, int val) {x = n - x + 1; while(x <= n) {t[x] = max(t[x], val); x += lowbit(x);}}
int query(int x) {x = n - x + 1; int ans = 0; while(x) {ans = max(ans, t[x]); x -= lowbit(x);} return ans;}
void clear() {for(int i=1; i<=n; i++) t[i] = 0;}
}T;
struct seg
{
struct node
{
int val, tag;
}t[maxn<<2];
void pushdown(int x)
{
int ls = x<<1, rs = x<<1|1;
t[ls].val += t[x].tag;
t[rs].val += t[x].tag;
t[ls].tag += t[x].tag;
t[rs].tag += t[x].tag;
t[x].tag = 0;
}
void pushup(int x)
{
t[x].val = max(t[x<<1].val, t[x<<1|1].val);
}
void modify(int x, int l, int r, int L, int R, int val)
{
if(L <= l && r <= R)
{
t[x].val += val; t[x].tag += val;
return;
}
if(t[x].tag) pushdown(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);
pushup(x);
}
int query(int x, int l, int r, int L, int R)
{
if(L <= l && r <= R) return t[x].val;
if(t[x].tag) pushdown(x);
int mid = (l + r) >> 1, ans = 0;
if(L <= mid) ans = max(ans, query(x<<1, l, mid, L, R));
if(R > mid) ans = max(ans, query(x<<1|1, mid+1, r, L, R));
return ans;
}
}t;
int main()
{
freopen("cheat.in", "r", stdin);
freopen("cheat.out", "w", stdout);
n = read();
for(int i=1; i<=n; i++) a[i] = read();
for(int i=1; i<=n; i++) l[i] = read(), r[i] = read();
for(int i=1; i<=n; i++)
{
T.modify(a[i], i); ll[i] = T.query(l[i]), lr[i] = T.query(r[i]+1);
}
T.clear();
for(int i=n; i>=1; i--)
{
T.modify(a[i], n-i+1); rl[i] = n-T.query(l[i])+1, rr[i] = n-T.query(r[i]+1)+1;
}
for(int i=1; i<=n; i++)
{
if(rl[i]) RL[rl[i]].push_back(i);
if(rr[i]) RR[rr[i]].push_back(i);
}
for(int i=1; i<=n; i++)
{
t.modify(1, 1, n, i, i, t.query(1, 1, n, 1, n));
if(ll[i]) t.modify(1, 1, n, 1, ll[i], 1);
if(lr[i]) t.modify(1, 1, n, 1, lr[i], -1);
for(int x : RL[i]) if(ll[x] < x) t.modify(1, 1, n, ll[x]+1, x, 1);
for(int x : RR[i]) if(lr[x] < x) t.modify(1, 1, n, lr[x]+1, x, -1);
}
printf("%d\n", t.query(1, 1, n, 1, n));
return 0;
}