C - Berry Jam
题意:给2n个罐子,分别是[1,2n],站在[n,n+1]的中间,吃掉包含这个点的左边连续一段和右边连续一段,使得剩下的罐子红色的和蓝色的数量相等。吃最少的罐子。
题解:把红色蓝色变成+1和-1,那么就把左边的前缀和插进map里面,然后右边的后缀和在map里面查询相反数,查到就更新。注意因为是吃得越少越好所以同一个值,map里存的应该是最靠右的前缀。
注意!要加上为0的前缀和和后缀和!
不过现场可以看出来并造样例验证,也是能力提高了一点。
4
1 1 1 1 1 2 1 2
这个PP可真良心。
int n;
int a[2000005];
int prefix[2000005];
int suffix[2000005];
map<int, int> M;
void test_case() {
scanf("%d", &n);
M.clear();
for(int i = 0; i <= 2 * n + 1; ++i) {
prefix[i] = 0;
suffix[i] = 0;
}
for(int i = 1; i <= 2 * n; ++i) {
scanf("%d", &a[i]);
if(a[i] == 2)
a[i] = -1;
}
for(int i = 1; i <= 2 * n; ++i)
prefix[i] = prefix[i - 1] + a[i];
for(int i = 2 * n; i >= 1; --i)
suffix[i] = suffix[i + 1] + a[i];
int ans = 0;
for(int i = 0; i <= n; ++i)
M[prefix[i]] = i;
for(int i = 2 * n + 1; i >= n + 1; --i) {
int t = -suffix[i];
if(M.count(t)) {
ans = max(ans, M[t] + 2 * n + 1 - i);
//cout << "i=" << M[t] << " j=" << 2 * n + 1 - i << endl;
}
}
printf("%d\n", 2 * n - ans);
}
D - Segment Tree
题意:给2n个数字,每个数字[1,n]恰好出现2次,第一次称为l[i],第二次称为r[i]。任何两个部分交叉的线段代表的点之间连边,问连出来的是不是树。
题解:平衡树按r排序,遇到l就插进平衡树里面,那么每次就取出r<当前节点的r的点全部连一条边。当连的边不少于n条或者成环(实际上连的边有n条就一定会成环)就不可能是一棵树。当然,连的边不是n-1条也不会是一棵树。
好像对于这种有序性的还是平衡树方便又快,用堆还要反反复复取出来,常数小但是多个log。
struct DisjointSetUnion {
static const int MAXN = 1000005;
int n, fa[MAXN + 5], rnk[MAXN + 5];
void Init(int _n) {
n = _n;
for(int i = 1; i <= n; i++) {
fa[i] = i;
rnk[i] = 1;
}
}
int Find(int u) {
int r = fa[u];
while(fa[r] != r)
r = fa[r];
int t;
while(fa[u] != r) {
t = fa[u];
fa[u] = r;
u = t;
}
return r;
}
bool Merge(int u, int v) {
u = Find(u), v = Find(v);
if(u == v)
return false;
else {
if(rnk[u] < rnk[v])
swap(u, v);
fa[v] = u;
rnk[u] += rnk[v];
return true;
}
}
} dsu;
int n;
int l[1000005], r[1000005];
int a[1000005];
set<pii> PQ;
void test_case() {
scanf("%d", &n);
PQ.clear();
dsu.Init(2 * n);
a[0] = a[2 * n + 1] = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d%d", &l[i], &r[i]);
a[l[i]] = i;
a[r[i]] = -i;
}
int cntedge = 0;
for(int i = 1; i <= 2 * n; ++i) {
if(a[i] > 0) {
int id = a[i];
//cout << "id=" << id << endl;
for(auto &e : PQ) {
if(r[id] < e.first)
break;
if(dsu.Merge(e.second, id)) {
//printf("%d - %d\n", e.second, id);
++cntedge;
if(cntedge >= n) {
puts("NO");
return;
}
} else {
//printf("%d - %d but \n", e.second, id);
puts("NO");
return;
}
}
PQ.insert({r[id], id});
} else {
PQ.erase(PQ.begin());
//int id = -a[i];
}
}
if(cntedge == n - 1)
puts("YES");
else
puts("NO");
}
E - Tests for problem D
题意:给一棵树,构造一个序列,使得D题可以复原这棵树。
题解:一开始看起来像广搜,但又不是广搜,又有点像深搜。在遍历一个点之后把它除父亲外所有的邻接点全部入栈然后把这个点的右边界弹出,就再也不会往后面连边了,而遍历这些邻接点的顺序就是先进先出,这样兄弟之间就是包含关系。注意!叶子节点是度数<=1的点!注意平凡的情况。
int root;
vector<int> G[500005];
void dfs1(int u, int p) {
if(G[u].size() <= 1) {
root = u;
return;
}
for(auto &v : G[u]) {
if(v == p)
continue;
dfs1(v, u);
if(root)
return;
}
}
int ans[1000005], cnt;
int pa[500005];
stack<int> st;
void dfs(int u) {
for(auto &v : G[u]) {
if(v == pa[u])
continue;
ans[++cnt] = v;
pa[v] = u;
st.push(v);
}
}
int l[500005], r[500005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n - 1; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs1(1, -1);
ans[++cnt] = root;
st.push(root);
pa[root] = -1;
while(st.size()) {
int u = st.top();
st.pop();
dfs(u);
ans[++cnt] = u;
}
for(int i = 1; i <= cnt; ++i) {
int t = ans[i];
if(l[t] == 0)
l[t] = i;
else
r[t] = i;
//printf("%d%c", ans[i], " \n"[i == cnt]);
}
for(int i = 1; i <= n; ++i)
printf("%d %d\n", l[i], r[i]);
}
但是仔细一想,这个树上嵌套序列不是很像dfn序吗?只是父亲不再是嵌套子树了,而是和子树部分交叉,所以根据dfs来改是很自然的。下次不要怕。
原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12074593.html
时间: 2024-11-05 22:58:01