2022NPIOA层联测13
数塔:
相等上传非常显然,重点是怎么二分(对于这种不知道更大的更优还是更小的更优的题,不知道选哪个二分模板。。)
大于等于和小于等于都可以,重要的是取等,就是保证答案在二分的区间内,
二分剩下的数是什么,剩下带等号的一方肯定合法,如果是大于等于,区间就向大的方向缩小
小于等于就向小的方向缩小
比它小的设为0,如果最上面的是0,说明答案小于它,否则答案大于等于它
用哪种形式二分的判断条件:找到答案的方向,找最大值=可行区间在左边,找最小值=可行区间在右边
格式配对的判断条件:边界是l和r相差1,目的是尽量尝试更优的情况
所以,找最大值,mid偏大+1再右移,找最小值,直接右移
实在不行那就试一下,看哪一版能过样例
code大于等于设为1
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
int T, l, r, n, a[maxn], b[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;
}
bool check(int mid)
{
for(int i=1; i<=2*n-1; i++)
{
b[i] = (a[i] >= mid);
}
for(int i=0; i<=n-1; i++)
{
if(b[n+i] == b[n+i+1]) return b[n+i];
if(b[n-i] == b[n-i-1]) return b[n-i];
}
return b[1];
}
int main()
{
freopen("pyramid.in", "r", stdin);
freopen("pyramid.out", "w", stdout);
T = read();
while(T--)
{
n = read();
l = 1, r = 2 * n - 1;
for(int i=l; i<=r; i++) a[i] = read();
while(l < r)
{
int mid = (l + r + 1) >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
printf("%d\n", l);
}
return 0;
}
code小于等于设为1
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
int T, l, r, n, a[maxn], b[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;
}
bool check(int mid)
{
for(int i=1; i<=2*n-1; i++)
{
b[i] = (a[i] <= mid);//如果偏要变号呢?
}
for(int i=0; i<=n-1; i++)
{
if(b[n+i] == b[n+i+1]) return b[n+i];
if(b[n-i] == b[n-i-1]) return b[n-i];
}
return b[1];
}
int main()
{
freopen("pyramid.in", "r", stdin);
freopen("pyramid.out", "w", stdout);
T = read();
while(T--)
{
n = read();
l = 1, r = 2 * n - 1;
for(int i=l; i<=r; i++) a[i] = read();
while(l < r)
{
int mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
}
return 0;
}
有一个类似的题,好像当初我也是想了半天二分的逻辑
排序:from 来自学长的馈赠5
当时的总结:二分的模板有两个,from进阶指南,一个是找满足条件的里尽量大的,另一个是找满足条件的里尽量小的,我本来以为要找大于等于的第一个,所以要用求尽量小的答案的模板,但是它不对,然后我忽然发现我枚举的是比大于等于当前数的标准,然后只要当前数在大于等于的范围内是符合条件,所以标准越小越容易满足条件,但是要找恰好的位置,所以这个大于等于的标准应该恰好卡死,也就是说其实要找最大的答案。
忽然发现这两次都遇到了一个概念,那就是我二分的这个标准到底是什么东西,二分的就是答案呀!
可以理解成:二分,找到一个尽量大的标准(>=某数)把当前查找的位置上的数包括进去,也可以理解成,二分当前位置的值是什么,如果推出矛盾就不合法
如果把大于等于mid的看成了1,如果这个mid合法就说明答案大于等于mid,扩大就是很自然的了
二分什么的。。不禁联想到了交互题?!
环游
搬个题解:V只会变化log2V次,每一层中可达范围形成若干个连续段,我们需要在每一层选一个连续段覆盖所有点。
状压dp,令preS表示当前选了S这些层,可以覆盖的最长前缀,l[i][j]表示长度满足第i层限制右端点在j的最大连续段的左端点的位置。
就是换一个角度考虑问题:
从每一个状态至多选一个,覆盖所有区间,可以是状态连续,区间跳来跳去,也可以是区间连续而状态自由组合。为什么可以跳?反正都是每个区间选一个,顺序并不重要啦。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 3;
int n, v, x[maxn], l[19][maxn], r[19][maxn], pre[1<<19], suf[1<<19];
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("tour.in", "r", stdin);
freopen("tour.out", "w", stdout);
n = read(); v = read();
for(int i=1; i<=n; i++) x[i] = read();
int mx = log2(v)+1;//关于为什么写作__lg(),我不知道,上百度没查着。。
for(int i=0; i<=mx; i++)
{
l[i][1] = 1; r[i][n] = n;
//l[]以j为右端点的段,左端点最长延伸到哪里
for(int j=1+1; j<=n; j++) if(x[j]-x[j-1]<=(v>>i)) l[i][j] = l[i][j-1]; else l[i][j] = j;
for(int j=n-1; j>=1; j--) if(x[j+1]-x[j]<=(v>>i)) r[i][j] = r[i][j+1]; else r[i][j] = j;
}
memset(suf, 0x3f, sizeof(suf));
suf[0] = n + 1;
for(int i=0; i<(1<<mx); i++)
{
for(int j=1; j<=mx; j++)
{
if((i>>(j-1))&1) continue;
pre[i|(1<<(j-1))] = max(pre[i|(1<<(j-1))], r[j][min(pre[i]+1, n)]);
suf[i|(1<<(j-1))] = min(suf[i|(1<<(j-1))], l[j][max(suf[i]-1, 1)]);
}
}
int cnt = 0;
for(int i=1; i<=n; i=r[0][i]+1) cnt++;
if(cnt > mx + 1)
{
for(int i=1; i<=n; i++) printf("Impossible\n");
}
else
{
for(int i=1; i<=n; i=r[0][i]+1)
{
bool fl = false;
for(int j=0; j<(1<<mx)&&!fl; j++) if(pre[j] >= i-1 && suf[((1<<mx)-1)^j] <= r[0][i]+1) fl = 1;
for(int j=i; j<=r[0][i]; j++) if(fl) printf("Possible\n"); else printf("Impossible\n");
}
}
return 0;
}
二择
审错题了,还以为选任意非空集合,那还没重边没自环,不就直接输出1?。。。
今天的官方题解写的真清楚:大小为n的匹配和大小为n的独立集加起来共3n个点,先求出一组极大匹配剩下来的点一定是独立集,两个要求中至少满足了一个。
code
/*
额,n个点或n条边,是我xia了
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1500005;
int T, n, m;
struct edge {int u, v;}e[maxn];
bool vis[maxn];
vector<int> 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;
}
int main()
{
freopen("choice.in", "r", stdin);
freopen("choice.out", "w", stdout);
T = read();
while(T--)
{
n = read(), m = read();
for(int i=1; i<=m; i++)
{
int u = read(), v = read();
e[i] = {u, v};
}
ans.clear();
for(int i=1; i<=n+n+n; i++) vis[i] = false;
for(int i=1; i<=m; i++)
{
int u = e[i].u, v = e[i].v;
if(vis[u] || vis[v]) continue;
vis[u] = vis[v] = 1;
ans.push_back(i);
}
if(ans.size() >= n)
{
printf("Beta2\n");
for(int i=0; i<n; i++) printf("%d ", ans[i]);
printf("\n"); continue;
}
ans.clear();
for(int i=1; i<=n+n+n; i++) if(!vis[i]) ans.push_back(i);
printf("Beta1\n");
for(int i=0; i<n; i++) printf("%d ", ans[i]);
printf("\n");
}
return 0;
}