20260929
P3514 [POI 2011] LIZ-Lollipop
很巧妙的思维题。
题意
给一个长为 \(n\) 且只有 \(1\) 和 \(2\) 的序列,\(m\) 次询问,每次询问有没有一个子串的和为 \(x\)。
\(1 \le n, m \le 10 ^ 6\)
思路
性质1:每个答案必定在最大的总和为奇/偶数的范围内。
证明:
考虑当前区间 \([l, r]\) 总和为 \(x\),求区间总和为 \(x - 2\) 的区间。
- \(a_l\) 为 \(2\) 则答案为 \([l + 1, r]\)。
- \(a_r\) 为 \(2\) 则答案为 \([l, r - 1]\)。
- 如果上面两种情况都不满足,则 \(a_l = a_r = 1\) 答案为 \([l + 1, r - 1]\)。
则我们分别预处理出奇数和偶数的最大区间和的区间,然后离线给答案从大到小排个序,双指针求出答案即可。
无解的情况就是 \(x\) 大于最大区间和。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, m, a[N], l, r, ma1, ma2, ll, rr, b[N];
struct Ans
{
int l, r;
}ans[N];
vector<Ans> q1, q2;
bool cmp(Ans a, Ans b)
{
return a.l > b.l;
}
string s;
signed main()
{
cin >> n >> m;
cin >> s;
for(int i = 1; i <= m; i ++)
{
int x;
scanf("%d", &x);
if(x % 2 == 0) q1.push_back({x, i});
else q2.push_back({x, i});
}
for(int i = 0; i < n; i ++)
{
a[i + 1] = (s[i] == 'T') + 1 + a[i];
b[i + 1] = (s[i] == 'T') + 1;
// cout << a[i + 1] << " " << b[i + 1] << endl;
}
for(int i = 1; i <= n; i ++)
{
int sum = a[i] - a[0], sum2 = a[n] - a[i - 1];
if(sum % 2 == 0)
{
if(sum > ma1) l = 1, r = i, ma1 = sum;
}
else
{
if(sum > ma2) ll = 1, rr = i, ma2 = sum;
}
if(sum2 % 2 == 0)
{
if(sum2 > ma1) l = i, r = n, ma1 = sum2;
}
else
{
if(sum2 > ma2) ll = i, rr = n, ma2 = sum2;
}
}
// cout << "qwq: " << l << " " << r << endl;
// cout << "awa: " << ll << " " << rr << endl;
sort(q1.begin(), q1.end(), cmp);
sort(q2.begin(), q2.end(), cmp);
for(Ans u : q1)
{
int v = u.l;
if(a[r] - a[l - 1] < v)
{
ans[u.r].l = -1;
ans[u.r].r = -1;
continue;
}
while(a[r] - a[l - 1] != v)
{
if(b[r] == 2) r --;
else if(b[l] == 2) l ++;
else r --, l ++;
}
ans[u.r].l = l, ans[u.r].r = r;
}
for(Ans u : q2)
{
int v = u.l;
if(a[rr] - a[ll - 1] < v)
{
ans[u.r].l = -1;
ans[u.r].r = -1;
continue;
}
while(a[rr] - a[ll - 1] != v)
{
if(b[rr] == 2) rr --;
else if(b[ll] == 2) ll ++;
else rr --, ll ++;
}
ans[u.r].l = ll, ans[u.r].r = rr;
}
for(int i = 1; i <= m; i ++)
{
if(ans[i].l == -1) printf("NIE\n");
else printf("%d %d\n", ans[i].l, ans[i].r);
}
}
P2279 [HNOI2003] 消防局的设立
题意
给定一棵有 \(n\) 个点的树。每次操作可以选定一个点,覆盖和这个点距离 \(2\) 以内的所有点(包括这个点),求最小操作次数。
距离的定义为两个点之间的边的数量。
\(n \le 1000\)
思路
看到这个数据时我是有点迷惑的,怀疑这题的算法时间复杂度是 \(O(n ^ 2 \log n)\) 的。
考虑贪心,不难发现,当一个点没有被覆盖的时候,选它的祖父点覆盖一定是最优的(因为它的祖父节点可以确保从它和它的兄弟到它的祖父的祖父全都被覆盖)。
那么我们怎么更高效的利用呢?考虑求出每个点的深度,按深度从大到小处理:
- 如果这个点已经被覆盖了,则不用考虑。
- 如果这个点没有祖父,则考虑覆盖父亲节点。
- 没有父亲则覆盖自己。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n, cov[1005], ans;
vector<int> ve[1005];
int dep[1005];
struct node
{
vector<int> son;
int fa;
}g[1005];
void dfs(int u, int fa)
{
g[u].fa = fa;
dep[u] = dep[fa] + 1;
for(int i = 0; i < ve[u].size(); i ++)
{
int v = ve[u][i];
if(v == fa) continue;
g[u].son.push_back(v);
dfs(v, u);
}
}
void col(int u, int sum, int lst)
{
if(sum > 2) return ;
cov[u] = 1;
for(int v : ve[u])
{
if(v == lst) continue;
col(v, sum + 1, u);
}
}
bool cmp(int a, int b)
{
return dep[a] > dep[b];
}
signed main()
{
cin >> n;
for(int i = 2; i <= n; i ++)
{
int x; cin >> x;
ve[x].push_back(i);
ve[i].push_back(x);
}
dep[0] = -1;
dfs(1, 0);
vector<int> id;
for(int i = 1; i <= n; i++) id.push_back(i);
sort(id.begin(), id.end(), cmp);
memset(cov, 0, sizeof(cov));
ans = 0;
for(int u : id)
{
if(!cov[u])
{
ans++;
int pos = u;
if(g[pos].fa != 0) pos = g[pos].fa;
if(g[pos].fa != 0) pos = g[pos].fa;
col(pos, 0, 0);
}
}
cout << ans << endl;
return 0;
}

浙公网安备 33010602011771号