2022NOIPA层联测19
A. 皮胚
和正解一样的dp式子都列出来了,然而由于做过的上一个通配符匹配的题过分鬼畜,先分段再按段dp,加加减减大力分讨,感觉这题虽然在T1却不可能那么简单,于是没有提交。。
没有提交的更直接的原因是过不了样例,然而没有自信的我以为是我的算法错了,没有发现我的dp数组没有清空。。
多测不清空,爆零两行泪。
赛后打开题解找到了自信和错因,瞬间清空了dp数组又交了一个0分,结果ans++没有归零,输出了个前缀和。然后ans=0就可以A了。
code
//accoders提醒你多测要清空!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2004;
int T, n, m, ans;
char a[maxn], b[maxn];
bool f[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;
}
int main()
{
freopen("match.in", "r", stdin);
freopen("match.out", "w", stdout);
T = read();
while(T--)
{
scanf("%s%s", a+1, b+1);
n = strlen(a+1); m = strlen(b+1);
memset(f, 0, sizeof(f)); ans = 0;
f[0][0] = 1;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if(a[i] == b[j] || b[j] == '.')
{
f[i][j] |= f[i-1][j-1];
}
if(b[j] == '*' && a[i] == a[i-1])
{
f[i][j] |= f[i-1][j];
}
if(b[j] == '*')//这一条没想到。。。
{
f[i][j] |= f[i][j-1];
}
}
}
for(int i=1; i<=n; i++)
{
if(f[i][m]) ans++;
}
printf("%d\n", ans);
}
return 0;
}
B. 核冰
直接清掉所有东西开始,每个修改都重来一次TLE 36.
code
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 3;
int n, m, a[maxn], cnt[maxn], c[maxn], ans, Max;
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;
}
int main()
{
freopen("merge.in", "r", stdin);
freopen("merge.out", "w", stdout);
n = read(); m = read();
for(int i=1; i<=n; i++) a[i] = read(), cnt[a[i]]++, Max = max(Max, a[i]);
while(m--)
{
int opt = read();
if(opt == 1)
{
int i = read(), x = read();
cnt[a[i]]--; cnt[x]++;
a[i] = x; Max = max(Max, x);
}
else
{
memcpy(c, cnt, sizeof(cnt));
ans = 0;
for(int i=1; i<=Max; i++)
{
if(c[i]) ans++;
else continue;
int t = (c[i] - 1) / 2;
c[i+1] += t; c[i] -= t * 2;
}
for(int i=Max+1; c[i]; i++)
{
ans++;
int t = (c[i] - 1) / 2;
c[i+1] += t; c[i] -= t * 2;
}
printf("%d\n", ans);
}
}
return 0;
}
说好了不鹤的呢,我怎么又忍不住鹤去了? s更新后不清空,留下来可以重复利用,修改之前预处理,改到相等就结束,感叹于神奇
保存初始个数和按照每个时刻划分的贪心的个数,用上一个有效的贪心结果和当前的实际数目从半途开始修改,到与修改前信息能接续时停止。
code
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 500100;
int cnt[maxn], s[maxn], n, m, a[maxn], ans;
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 change()
{
int pos = read(), x = read();
cnt[a[pos]]--;
for(int i=a[pos]; i<maxn; i++)
{
int now = cnt[i];
if(s[i-1] & 1) now += s[i-1] / 2;
else if(s[i-1]) now += (s[i-1] - 2) / 2;
if(now == s[i]) break;
s[i] = now;
ans -= s[i] == 0;
}
cnt[a[pos]=x]++;
for(int i=a[pos]; i<maxn; i++)
{
int now = cnt[i];
if(s[i-1] & 1) now += s[i-1] / 2;
else if(s[i-1]) now += (s[i-1] - 2) / 2;
if(now == s[i]) break;
ans += s[i] == 0;
s[i] = now;
}
}
int main()
{
freopen("merge.in", "r", stdin);
freopen("merge.out", "w", stdout);
n = read(); m = read();
for(int i=1; i<=n; i++) cnt[a[i]=read()]++;
for(int i=1; i<maxn; i++)
{
s[i] = cnt[i];
if(s[i-1] & 1) s[i] += s[i-1] / 2;
else if(s[i-1]) s[i] += (s[i-1] - 2) / 2;
ans += s[i] > 0;
}
for(int i=1; i<=m; i++)
{
int op = read();
if(op & 1) change();
else printf("%d\n", ans);
}
return 0;
}
我真的不想鹤。。。 在线段树上二分什么,insert鹤erase的返回值是什么含义? 二分更新的边界,返回值就是这个边界,=r表示超越了区间,否则在内部能完成。
和暴力的区别就是,一个对于可以传递的区间挨个跳,而另一个用线段树上二分的形式一次跳很远。其实线段树本来就是用二分思想建成的,用到线段树应该都可以称为二分?
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 500107, M = 5e5 + 100;
int cnt[maxn], n, m, a[maxn], ans;
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 seg
{
struct node
{
int min, lazy;
bool mod[2];
}t[maxn<<2];
void pushup(int x)
{
int ls = x<<1, rs = x<<1|1;
t[x].mod[0] = t[x].mod[1] = 0;
//只有相等才可以传递吗?why??
//1代表可以,0不代表不行,所以只有1是有用的,0相当于空白
//表示区间加减的结果有传递性
if(t[ls].mod[0] && t[rs].mod[0]) t[x].mod[0] = 1;
else if(t[ls].mod[1] && t[rs].mod[1]) t[x].mod[1] = 1;
t[x].min = min(t[ls].min, t[rs].min);
}
void add(int x, int val)
{
t[x].min += val;
t[x].lazy += val;
if(val & 1)
{
if(t[x].mod[0]) t[x].mod[1] = 1, t[x].mod[0] = 0;
else if(t[x].mod[1]) t[x].mod[0] = 1, t[x].mod[1] = 0;
}
}
void pushdown(int x)
{
int ls = x<<1, rs = x<<1|1;
if(t[x].lazy)
{
add(ls, t[x].lazy); add(rs, t[x].lazy);
t[x].lazy = 0;
}
}
void build(int x, int l, int r)
{
if(l == r)
{
t[x].min = cnt[l];
if(cnt[l] & 1) t[x].mod[1] = 1, t[x].mod[0] = 0;
else t[x].mod[1] = 0, t[x].mod[0] = 1;
return;
}
int mid = (l + r) >> 1;
build(x<<1, l, mid);
build(x<<1|1, mid+1, r);
pushup(x);
}
int insert(int x, int l, int r, int pos)
{
if(l >= pos)
{
//上传区间不能终止的情况,有值且偶数个,区间加1连续上传直接到区间外面了
//求min也是为了连续性
if(t[x].mod[0] && t[x].min)//最右边一定不会进,因为min不会有值的
{
add(x, 1); return r;//如果是左区间,到右区间继续传递
}
if(l == r)//传递到此终止,因为连这个只有一个点的区间都不满足上面可以跳过的情况
{
add(x, 1); if(t[x].min == 1) ans++;
return r - 1;
}
pushdown(x);
int mid = (l + r) >> 1;
int y = insert(x<<1, l, mid, pos);
if(y == mid) y = insert(x<<1|1, mid+1, r, pos);
pushup(x);
return y;
}
int mid = (l + r) >> 1, y;
pushdown(x);
//向下递归找上传的起点,返回值是影响的范围->上传的终点
//根据终点判断是否需要连续上传
if(pos <= mid)
{
y = insert(x<<1, l, mid, pos);
if(y == mid) y = insert(x<<1|1, mid+1, r, pos);
}
else y = insert(x<<1|1, mid+1, r, pos);
pushup(x);
return y;
}
int erase(int x, int l, int r, int pos)
{
if(l >= pos)
{
if(t[x].mod[1] && t[x].min != 1)
{
add(x, -1); return r;
}
if(l == r)
{
add(x, -1); if(!t[x].min) ans--;
return r - 1;
}
pushdown(x);
int mid = (l + r) >> 1;
int y = erase(x<<1, l, mid, pos);
if(y == mid) y = erase(x<<1|1, mid+1, r, pos);
pushup(x);
return y;
}
int mid = (l + r) >> 1, y;
pushdown(x);
if(pos <= mid)
{
y = erase(x<<1, l, mid, pos);
if(y == mid) y = erase(x<<1|1, mid+1, r, pos);
}
else y = erase(x<<1|1, mid+1, r, pos);
pushup(x);
return y;
}
}t;
int main()
{
freopen("merge.in", "r", stdin);
freopen("merge.out", "w", stdout);
n = read(); m = read();
for(int i=1; i<=n; i++) a[i] = read(), cnt[a[i]]++;
for(int i=1; i<=M; i++)//这个进位的上限是感性定的吗?
{
if(!cnt[i]) continue;
ans++; cnt[i+1] += (cnt[i] - 1) / 2;
}
t.build(1, 1, M);
for(int i=1; i<=m; i++)
{
int opt = read();
if(opt == 2) printf("%d\n", ans);
else
{
int pos = read(), x = read();
t.erase(1, 1, M, a[pos]);
t.insert(1, 1, M, x); a[pos] = x;
}
}
return 0;
}
C. 方珍
Step 1:No think 暴力:5 pts
code
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e3 + 3;
int n, k, w[maxn], f[maxn], mex, m, a[maxn], e[maxn*maxn], tot;
bool vis[maxn];
ll ans;
ull seed;
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;
}
ull _rand()
{
seed^=seed<<13;
seed^=seed>>7;
seed^=seed<<17;
return seed;
}
int main()
{
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
n = read();
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%llu",&k, &w[i], &m, &seed);
for(int j=1; j<=n; j++) a[j] = _rand() % m;
tot = 0;
for(int l=1; l<=n; l++)
{
mex = 0;
for(int r=l; r<=n; r++)
{
vis[a[r]] = 1;
while(vis[mex]) mex++;
e[++tot] = mex;
}
for(int r=l; r<=n; r++) vis[a[r]] = 0;
}
sort(e+1, e+1+tot);
f[i] = e[k];
}
for(int i=1; i<=n; i++)
{
ans = max(ans, (ll)f[i] + w[i]);
}
printf("%lld\n", ans);
return 0;
}
Step 2:看到mex就想到了线段树和移动左端点改变的只有当前位置上的值到它下一次出现之间,要么保持原数要么改为a[i],线段树不会了,但是区间上的东西可以暴力拆开 10 pts
原来不是所有的mex都可以用套路啊……
code
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e4 + 3;
int n, k, w[maxn], f[maxn], m, a[maxn], pos[maxn], mex[maxn], mx;
int nxt[maxn], cnt[maxn];
bool vis[maxn];
ll ans;
ull seed;
typedef pair<int, int> pii;
multiset<pii> s;
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;
}
ull _rand()
{
seed^=seed<<13;
seed^=seed>>7;
seed^=seed<<17;
return seed;
}
/*struct node
{
struct tree
{
int min, tag;
}t[maxn<<2];
void build(int x, int l, int r)
{
t[x].tag = -1;
if(l == r)
{
t[x].min = mex[l];
return;
}
int mid = (l + r) >> 1;
build(x<<1, l, mid);
build(x<<1|1, mid+1, r);
t[x].min = min(t[x<<1].min, t[x<<1|1].min);
}
void pushdown(int x)
{
int ls = x << 1, rs = x << 1 | 1;
t[ls].min = t[ls].tag = t[x].tag;
t[rs].min = t[rs].tag = t[x].tag;
}
void update(int x, int l, int r, int L, int R, int dat)
{
if(L <= l && r <= R)
{
t[x].min = dat;
t[x].tag = dat; return;
}
int mid = (l + r) >> 1;
if(t[x].tag != -1) pushdown(x);
if(L <= mid) update(x<<1, l, mid, L, R, dat);
if(R > mid) update(x<<1|1, mid+1, r, L, R, dat);
return;
}
int query(int x, int l, int r, int pos)
{
if(l == r) return t[x].min;
int mid = (l + r) >> 1;
if(t[x].tag != -1) pushdown(x);
if(pos <= mid) return query(x<<1, l, mid, pos);
else return query(x<<1|1, mid+1, r, pos);
}
}t;
void solve(int j)
{
if(t.query(1, 1, n, nxt[j]-1) <= a[j])
{
for(int i=j; i<=nxt[j]-1; i++)
{
cnt[t.query(1, 1, n, i)]++;
}
return;
}
int l = j, r = nxt[j] - 1;
while(l < r)
{
int mid = (l + r) >> 1;
if(t.query(1, 1, n, mid) > a[j]) r = mid;
else l = mid + 1;
}
cnt[a[j]] += nxt[j] - l;
for(int i=j; i<l; i++)
{
cnt[t.query(1, 1, n, i)]++;
}
}*/
int main()
{
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
n = read();
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%llu",&k, &w[i], &m, &seed);
for(int j=1; j<=n; j++) a[j] = _rand() % m;
//for(int j=1; j<=n; j++) printf("%d ", a[j]);
//printf("\n");
for(int j=0; j<=m; j++) vis[j] = 0, cnt[j] = 0;
mx = 0;
for(int j=1; j<=n; j++)
{
vis[a[j]] = 1;
while(vis[mx]) mx++;
mex[j] = mx;
//printf("mex[%d] = %d\n", j, mex[j]);
cnt[mx]++;
}
//t.build(1, 1, n);
for(int j=1; j<=n; j++) pos[a[j]] = n+1;
for(int j=n; j>=1; j--)
{
nxt[j] = pos[a[j]];
pos[a[j]] = j;
}
//for(int j=1; j<=n; j++) printf("%d ", nxt[j]);
//printf("\n");
for(int j=1; j<=n; j++)
{
//solve(j);
//t.update(1, 1, n, j, nxt[j]-1, a[j]);
//printf("j = %d\n", j);
for(int p=j+1; p<nxt[j]; p++)
{
if(mex[p] > a[j]) mex[p] = a[j];
//printf("mex[%d] = %d\n", p, mex[p]);
cnt[mex[p]]++;
}
for(int p=nxt[j]; p<=n; p++)
{
cnt[mex[p]]++;
//printf("mex[%d] = %d\n", p, mex[p]);
}
}
for(int j=1; j<=m; j++) cnt[j] += cnt[j-1];
f[i] = lower_bound(cnt, cnt+1+m, k)-cnt;
}
for(int i=1; i<=n; i++)
{
ans = max(ans, (ll)f[i] + w[i]);
//printf("f[%d] = %d\n", i, f[i]);
}
printf("%lld\n", ans);
return 0;
}
Step 3:有一个m很小的,提示我们可能重复的很多到最后几乎所有区间的mex都是m,直接令mex=m,结合Step 2可以水到55 pts
code
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e4 + 3;
int n, k, w[maxn], f[maxn], m, a[maxn], pos[maxn], mex[maxn], mx;
int nxt[maxn], cnt[maxn];
bool vis[maxn];
ll ans;
ull seed;
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;
}
ull _rand()
{
seed^=seed<<13;
seed^=seed>>7;
seed^=seed<<17;
return seed;
}
void solve()
{
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%llu",&k, &w[i], &m, &seed);
//for(int j=1; j<=n; j++) a[j] = _rand() % m;
f[i] = m;
}
for(int i=1; i<=n; i++)
{
ans = max(ans, (ll)f[i] + w[i]);
}
printf("%lld\n", ans);
exit(0);
}
int main()
{
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
n = read();
if(n > 3000) solve();
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%llu",&k, &w[i], &m, &seed);
for(int j=1; j<=n; j++) a[j] = _rand() % m;
for(int j=0; j<=m; j++) vis[j] = 0, cnt[j] = 0;
mx = 0;
for(int j=1; j<=n; j++)
{
vis[a[j]] = 1;
while(vis[mx]) mx++;
mex[j] = mx;
cnt[mx]++;
}
for(int j=1; j<=n; j++) pos[a[j]] = n+1;
for(int j=n; j>=1; j--)
{
nxt[j] = pos[a[j]];
pos[a[j]] = j;
}
for(int j=1; j<=n; j++)
{
for(int p=j+1; p<nxt[j]; p++)
{
if(mex[p] > a[j]) mex[p] = a[j];
cnt[mex[p]]++;
}
for(int p=nxt[j]; p<=n; p++)
{
cnt[mex[p]]++;
}
}
for(int j=1; j<=m; j++) cnt[j] += cnt[j-1];
f[i] = lower_bound(cnt, cnt+1+m, k)-cnt;
}
for(int i=1; i<=n; i++)
{
ans = max(ans, (ll)f[i] + w[i]);
}
printf("%lld\n", ans);
return 0;
}
Step 4:认为以上水分方法虽然有来源但是很玄学,于是把w从大到小排了个序,w越小越用它的f更新答案的可能性越小,蒙了个数307,算到这就不算了,可以水15 pts,于是我的55 pts被覆盖掉了,考试结束。。
code
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e4 + 3;
int n, k, w[maxn], f[maxn], m, a[maxn], pos[maxn], mex[maxn], mx;
int nxt[maxn], cnt[maxn];
bool vis[maxn];
ll ans;
ull seed;
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;
}
ull _rand()
{
seed^=seed<<13;
seed^=seed>>7;
seed^=seed<<17;
return seed;
}
int ks[maxn], ms[maxn], seeds[maxn], id[maxn];
bool cmp(int x, int y)
{
return w[x] > w[y];
}
void solve()
{
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%llu",&ks[i], &w[i], &ms[i], &seeds[i]);
id[i] = i;
}
sort(id+1, id+1+n);
for(int i=1; i<=307; i++)
{
k = ks[id[i]]; m = ms[id[i]]; seed = seeds[id[i]];
for(int j=1; j<=n; j++) a[j] = _rand() % m;
for(int j=0; j<=m; j++) vis[j] = 0, cnt[j] = 0;
mx = 0;
for(int j=1; j<=n; j++)
{
vis[a[j]] = 1;
while(vis[mx]) mx++;
mex[j] = mx;
cnt[mx]++;
}
for(int j=1; j<=n; j++) pos[a[j]] = n+1;
for(int j=n; j>=1; j--)
{
nxt[j] = pos[a[j]];
pos[a[j]] = j;
}
for(int j=1; j<=n; j++)
{
for(int p=j+1; p<nxt[j]; p++)
{
if(mex[p] > a[j]) mex[p] = a[j];
cnt[mex[p]]++;
}
for(int p=nxt[j]; p<=n; p++)
{
cnt[mex[p]]++;
}
}
for(int j=1; j<=m; j++) cnt[j] += cnt[j-1];
f[i] = lower_bound(cnt, cnt+1+m, k)-cnt;
ans = max(ans, (ll)f[i] + w[i]);
}
printf("%lld\n", ans);
exit(0);
}
int main()
{
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
n = read();
if(n > 3000) solve();
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%llu",&k, &w[i], &m, &seed);
for(int j=1; j<=n; j++) a[j] = _rand() % m;
for(int j=0; j<=m; j++) vis[j] = 0, cnt[j] = 0;
mx = 0;
for(int j=1; j<=n; j++)
{
vis[a[j]] = 1;
while(vis[mx]) mx++;
mex[j] = mx;
cnt[mx]++;
}
for(int j=1; j<=n; j++) pos[a[j]] = n+1;
for(int j=n; j>=1; j--)
{
nxt[j] = pos[a[j]];
pos[a[j]] = j;
}
for(int j=1; j<=n; j++)
{
for(int p=j+1; p<nxt[j]; p++)
{
if(mex[p] > a[j]) mex[p] = a[j];
cnt[mex[p]]++;
}
for(int p=nxt[j]; p<=n; p++)
{
cnt[mex[p]]++;
}
}
for(int j=1; j<=m; j++) cnt[j] += cnt[j-1];
f[i] = lower_bound(cnt, cnt+1+m, k)-cnt;
}
for(int i=1; i<=n; i++)
{
ans = max(ans, (ll)f[i] + w[i]);
}
printf("%lld\n", ans);
return 0;
}
Step 5:鹤算法一,挨个二分,一开始以为check应该写成这样子:check(mid)<=k && check(mid+1)-1>=k,后来发现这个不一定,还有一个细节就是mid=0的时候所有的都满足条件可以直接反回sum,否则让l一直加可能加到离谱停不下来,塔特判一下l < r也可以,不过我两个都加上了。 accoders TLE 30 pts hz题库 TLE 30 pts
code
// ubsan: undefined
// accoders
/*
m是第k小但可以有并列,求出了所有mex>=m的区间数量,总数减掉这个数不一定等于k-1
TLE 30??难道二分不是这么写??
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e4 + 3;
int n, k, w[maxn], f[maxn], m, a[maxn], mex[maxn], mx;
int nxt[maxn], cnt[maxn], sum, tot;
bool vis[maxn];
ll ans;
ull seed;
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;
}
ull _rand()
{
seed^=seed<<13;
seed^=seed>>7;
seed^=seed<<17;
return seed;
}
int check(int mid)
{
if(mid == 0) return sum;
mx = 0; tot = 0;
int p = 0, l = 1;
for(int i=0; i<=m; i++) vis[i] = 0, cnt[i] = 0;
for(int i=1; i<=n; i++)
{
vis[a[i]] = 1; cnt[a[i]]++;
while(vis[mx]) mx++;
if(mx >= mid) {p = i; tot++; break;}
}
if(mx < mid) return sum;
for(int i=p+1; i<=n; i++)
{
cnt[a[i]]++;
while((a[l] >= mid || cnt[a[l]] > 1) && l < i) {cnt[a[l]]--; l++;}
tot += l;
}
return sum - tot + 1;
}
int main()
{
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
n = read(); sum = n * (n + 1) / 2;
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%llu",&k, &w[i], &m, &seed);
for(int j=1; j<=n; j++) a[j] = _rand() % m;
int l = 0, r = m;
while(l < r)
{
int mid = (l + r + 1) >> 1;
if(check(mid)<=k) l = mid;
else r = mid - 1;
}
f[i] = l;
}
for(int i=1; i<=n; i++)
{
ans = max(ans, (ll)f[i] + w[i]);
}
printf("%lld\n", ans);
return 0;
}
Step 6:鹤算法二,ans指的是最终要输出的结果,按w降序排序之后想要更新答案就需要达到ans+1同时还要-w[i]来弥补自带的差值,二分第一个答案为ans指针的起点,由于wi递减,发现ans最多增加n次。 accoders TLE 30 pts hz题库 AC
code
/*
m是第k小但可以有并列,求出了所有mex>=m的区间数量,总数减掉这个数不一定等于k-1
TLE 30??难道二分不是这么写??
继续改算法2,结果每个样例都多了个1??我要找尽量大的mex,可是它不能大到这个大小的最小排名都大于k,
感觉没什么问题??首先我的check已经检验了是对的,那是什么错了??
因为忘了把a算出来导致a全是0所以l=1
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e4 + 3;
int n, m, k, a[maxn], mx, cnt[maxn], sum, tot;
bool vis[maxn];
ll ans;
ull seed;
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;
}
ull _rand()
{
seed^=seed<<13;
seed^=seed>>7;
seed^=seed<<17;
return seed;
}
int check(int mid)
{
if(mid == 0) return sum;
mx = 0; tot = 0;
int p = 0, l = 1;
for(int i=0; i<=m; i++) vis[i] = 0, cnt[i] = 0;
for(int i=1; i<=n; i++)
{
vis[a[i]] = 1; cnt[a[i]]++;
while(vis[mx]) mx++;
if(mx >= mid) {p = i; tot++; break;}
}
if(mx < mid) return sum;
for(int i=p+1; i<=n; i++)
{
cnt[a[i]]++;
while((a[l] >= mid || cnt[a[l]] > 1) && l < i) {cnt[a[l]]--; l++;}
tot += l;
}
return sum - tot + 1;
}
struct node
{
int k, w, m;
ull seed;
bool operator < (const node &T) const
{
return w > T.w;
}
}p[maxn];
int main()
{
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
n = read(); sum = n * (n + 1) / 2;
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%llu",&p[i].k, &p[i].w, &p[i].m, &p[i].seed);
}
sort(p+1, p+1+n);
m = p[1].m, k = p[1].k, seed = p[1].seed;
for(int j=1; j<=n; j++) a[j] = _rand() % m;
int l = 0, r = m;
while(l < r)
{
int mid = (l + r + 1) >> 1;
if(check(mid)<=k) l = mid;
else r = mid - 1;
}
ans = (ll)l + p[1].w;
//printf("l = %d w = %d\n", l, p[1].w);
//printf("--ans = %lld\n", ans);
//不是,从这开始错的那算法1那版是怎么搞的!!?
//我懂了,求第一个值的时候没有计算a数组!!
for(int i=2; i<=n; i++)
{
m = p[i].m; seed = p[i].seed; k = p[i].k;
for(int j=1; j<=n; j++) a[j] = _rand() % m;
while(check(ans+1-p[i].w)<=k) ans++;
//printf("ans = %lld\n", ans);
}
printf("%lld\n", ans);
return 0;
}
就算我改完了qwq
Stap 6:听到了讲题,讲题的同学提供了一种剪枝方法,have a little try 哇哦它A了!!!!!!!!!!!!!!!!!!!!在TLEcoders上A了!!!!!!!!!!
最后再放一下官方题解的精华部分:二分答案m,求出所有mex>=m的区间数量,枚举右端点r,维护0~m-1都出现的最大左端点l。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e4 + 3;
int n, m, k, a[maxn], mx, cnt[maxn], sum, tot;
bool vis[maxn];
ll ans;
ull seed;
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;
}
ull _rand()
{
seed^=seed<<13;
seed^=seed>>7;
seed^=seed<<17;
return seed;
}
int check(int mid)
{
if(mid == 0) return sum;
mx = 0; tot = 0;
int p = 0, l = 1;
for(int i=0; i<=m; i++) vis[i] = 0, cnt[i] = 0;
for(int i=1; i<=n; i++)
{
vis[a[i]] = 1; cnt[a[i]]++;
while(vis[mx]) mx++;
if(mx >= mid) {p = i; tot++; break;}
}
if(mx < mid) return sum;
for(int i=p+1; i<=n; i++)
{
cnt[a[i]]++;
while((a[l] >= mid || cnt[a[l]] > 1) && l < i) {cnt[a[l]]--; l++;}
tot += l;
}
return sum - tot + 1;
}
struct node
{
int k, w, m;
ull seed;
bool operator < (const node &T) const
{
return w > T.w;
}
}p[maxn];
int main()
{
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
n = read(); sum = n * (n + 1) / 2;
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%llu",&p[i].k, &p[i].w, &p[i].m, &p[i].seed);
}
sort(p+1, p+1+n);
m = p[1].m, k = p[1].k, seed = p[1].seed;
for(int j=1; j<=n; j++) a[j] = _rand() % m;
int l = 0, r = m;
while(l < r)
{
int mid = (l + r + 1) >> 1;
if(check(mid)<=k) l = mid;
else r = mid - 1;
}
ans = (ll)l + p[1].w;
for(int i=2; i<=n; i++)
{
m = p[i].m; seed = p[i].seed; k = p[i].k;
if(p[i].w + m <= ans) continue;
for(int j=1; j<=n; j++) a[j] = _rand() % m;
while(check(ans+1-p[i].w)<=k) ans++;
//printf("ans = %lld\n", ans);
}
printf("%lld\n", ans);
return 0;
}