T1[图论:并查集维护寻找特殊环]给出一个无向图,点权是0或者1,你可以从任意起点出发,每到达一个点,把这个点的权值放到你构造的字符串的末尾,并且这个点的权值取反。给出K次操作,添加一条\(u--v\)的边,输出每次操作后是否存在一种遍历方式使得可以无限构造字符串\('sbsbsbsbs'或者'bsbsbsbsbsb'\)。(n<=2e5,K<=2e5)
考场
一看到无限肯定是要找环,不然总有走尽的时候。然后考虑是长什么样的环:模拟一下发现就是奇环,而且相邻边是01,(只有一个地方不是),奇数是为了保证最后没有地方走了刚好接上。开始考虑应该怎么找环,拿出来不现实,太多了,突然想到可以枚举边用并查集维护已经是01相邻的点集合,因为01具有传递性。然后直接判断每对相邻点是11或者00的是否在同一并查集内。\(O(nlogn)\)
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define chu printf
#define ll long long
#define ull unsigned long long
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 1e5 + 100;
int n, m, q, fa[N], rfa[N];
char s[N];
vector<int> e[N];
struct operation {
int x, y;
} op[N];
int del[N];
inline int father(int x) { return (x == fa[x]) ? x : (fa[x] = father(fa[x])); }
inline bool check(int cut) {
// chu("check:%d\n",cut);
for (rint i = 1; i <= n; ++i) fa[i] = rfa[i], del[i] = 0;
for (rint i = 1; i <= cut; ++i) {
if (s[op[i].x] != s[op[i].y]) {
fa[father(op[i].x)] = father(op[i].y);
} else
e[op[i].x].push_back(op[i].y), del[op[i].x]++; //只加入一对就可以
}
bool yes = 0;
for (rint i = 1; i <= n; ++i) {
int x = father(i);
for (rint to : e[i]) {
if (father(to) == x) {
// chu("%d and %d can merge\n",to,i);
yes = 1;
break;
}
}
if (yes)
break;
}
for (rint i = 1; i <= n; ++i) {
while (del[i]) e[i].pop_back(), del[i]--;
}
// chu("the rel:%d\n",yes);
return yes;
}
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("bssb.in", "r", stdin);
freopen("bssb.out", "w", stdout);
n = re(), m = re(), q = re();
scanf("%s", s + 1);
for (rint i = 1; i <= n; ++i) fa[i] = i;
for (rint i = 1; i <= m; ++i) {
int x = re(), y = re();
if (s[x] != s[y]) {
fa[father(x)] = father(y);
} else
e[x].push_back(y); // chu("push back:%d %d\n",x,y);
}
for (rint i = 1; i <= n; ++i) rfa[i] = fa[i];
for (rint i = 1; i <= q; ++i) op[i].x = re(), op[i].y = re();
if (check(0)) {
// chu("in\n");
for (rint i = 1; i <= q; ++i) chu("Yes\n");
return 0;
}
int l = 1, r = q, ans = q + 1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid))
ans = mid, r = mid - 1;
else
l = mid + 1;
}
for (rint i = 1; i < ans; ++i) chu("No\n");
for (rint i = ans; i <= q; ++i) chu("Yes\n");
return 0;
}
/*
3 0 3
sbb
1 2
1 3
2 3
5 3 2
sbsbs
1 2
2 3
3 4
1 5
4 5
*/
T2[计数DP]定义句子是由主语谓语宾语构成,文章由句子构成,句子的主语和宾语成分都可以是句子。给出n个单词,标明词性可以是什么,求合法文章方案数。(n<=5e5)
考场
认为合法句子是形似\(abababababcbcbcbcbc..\)结构,但是总是算不对
正解
漏情况啦!句子可以是\(ab*b*b*b*b*bc\)的结构,因为句子和句子也可以接在一起,然后的DP就很显然了。
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define chu printf
#define ll long long
#define ull unsigned long long
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 5e5 + 100, mod = 998244353;
int f[N][3], n;
char s[N][3];
inline void upd(int& x, int y) {
x = x + y;
x = (x >= mod) ? (x - mod) : x;
}
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("paper.in", "r", stdin);
freopen("paper.out", "w", stdout);
n = re();
for (rint i = 1; i <= n; ++i) scanf("%s", s[i]);
for (rint i = 1; i <= n; ++i) {
if (s[i][0] == 'T')
upd(f[i][0], (f[i - 1][2] + f[i - 1][1] + 1) % mod);
if (s[i][1] == 'T')
upd(f[i][1], (f[i - 1][0] + f[i - 1][2]) % mod);
if (s[i][2] == 'T')
upd(f[i][2], f[i - 1][1]);
upd(f[i][0], f[i - 1][0]);
upd(f[i][1], f[i - 1][1]);
upd(f[i][2], f[i - 1][2]);
}
chu("%d", f[n][2]);
return 0;
}
/*
f[i][a/b/c]
f[i][a]=f[i-1][c]+f[i-1][b]+1
f[i][b]=f[i-1][a]+f[i-1][c]
f[i][c]=f[i-1][b]
*/
T3[数据结构:线段树/问题转化]给出一个操作序列a,1代表\(x/2\),2代表\(x*2\),3代表\(x*2+1\),4,5,6分别代表把这些操作无限重复直到不合法为止(要求数字的值域是\([1,2^n-1]\))。给出多次询问表示求顺次重复\([l,r]\)序列的操作对x进行,最后x的结果。(n,m<=2e5)
考场
暴力,本来想优化从最后一个4开始,但是懒,后来听说这么干有76分?
正解
就是很奇妙!把操作转化成二叉树上的跳父亲和左儿子右儿子,发现限制只和x的深度有关(也必须和深度有关),于是线段树上维护\((a,b,c)\)表示把操作转化成\((x/(2^a)*(2^b)+c)\)的形式进行区间合并,需要维护m个不同长度,只在初始化时考虑赋值就可以(只和长度有关嘛!)
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define chu printf
#define ll long long
#define ull unsigned long long
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 2e5 + 100;
int n, m, q, p[N];
char s[N];
struct node {
int a, b, c;
inline int solve(int x) { return ((x >> a) << b) | c; }
} t[N << 2][31];
inline node merge(node x, node y) {
int a = x.a, b = x.b, c = x.c, d = y.a, e = y.b, f = y.c;
if (b > d) {
return (node){ a, e + b - d, ((c >> d) << e) + f };
} else {
return (node){ a + d - b, e, ((c >> d) << e) + f };
}
}
inline void get(int k, int r) // k是指针,合并子树信息,是pushup干的事,我是要初始化
{
if (r == 1) {
t[k][0] = (node){ 0, 0, 0 };
for (rint i = 1; i < m; ++i) t[k][i] = (node){ 1, 0, 0 };
} else if (r == 2) {
t[k][m - 1] = (node){ 0, 0, 0 };
for (rint i = 0; i < m - 1; ++i) t[k][i] = (node){ 0, 1, 0 };
} else if (r == 3) {
t[k][m - 1] = (node){ 0, 0, 0 };
for (rint i = 0; i < m - 1; ++i) t[k][i] = (node){ 0, 1, 1 };
} else if (r == 4) {
for (rint i = 0; i < m; ++i) t[k][i] = (node){ i, 0, 0 };
} else if (r == 5) {
for (rint i = 0; i < m; ++i) t[k][i] = (node){ 0, m - 1 - i, 0 };
} else {
for (rint i = 0; i < m; ++i) t[k][i] = (node){ 0, m - 1 - i, (1 << (m - 1 - i)) - 1 };
}
}
#define lson (rt << 1)
#define rson (rt << 1 | 1)
inline void pushup(int rt) {
for (rint i = 0; i < m; ++i) {
node x = t[lson][i];
int len = i - x.a + x.b;
t[rt][i] = merge(t[lson][i], t[rson][len]);
}
}
inline void build(int rt, int l, int r) {
if (l == r) {
get(rt, p[l]);
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
pushup(rt);
}
inline void change(int rt, int l, int r, int pos, int vl) {
if (l == r) {
get(rt, vl);
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
change(lson, l, mid, pos, vl);
else
change(rson, mid + 1, r, pos, vl);
pushup(rt);
}
inline node query(int rt, int l, int r, int L, int R, int key) {
if (L <= l && r <= R)
return t[rt][key];
int mid = (l + r) >> 1;
if (R <= mid)
return query(lson, l, mid, L, R, key);
if (L > mid)
return query(rson, mid + 1, r, L, R, key);
node ans1 = query(lson, l, mid, L, R, key);
node ans2 = query(rson, mid + 1, r, L, R, key - ans1.a + ans1.b);
return merge(ans1, ans2);
}
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("calculate.in", "r", stdin);
freopen("calculate.out", "w", stdout);
n = re(), m = re(), q = re();
scanf("%s", s + 1);
for (rint i = 1; i <= n; ++i) p[i] = s[i] - '0';
build(1, 1, n);
for (rint i = 1; i <= q; ++i) {
int opt = re();
if (opt == 1) {
int l = re(), r = re(), x = re();
chu("%d\n", query(1, 1, n, l, r, log2(x)).solve(x));
} else {
int x = re(), y = re();
change(1, 1, n, x, y);
}
}
return 0;
}
/*
*/
T4[可反悔贪心]玩游戏有n个关卡,每个关卡1星花费a元,2星b元,3星c元,求最终或者1星及以上p个,2星及以上q个,3星r个最少花费。(n<=2e5)
考场
可反悔贪心不会写,直接DP还挂了,为什么挂了呢?因为理解错题目含义了,你完全可以全部选r,没有必要刚好选qpr的个数,而且dp我忘记继承前面的最小值了,导致他只能计算连续物品组合。
20pts
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define chu printf
#define ll long long
#define ull unsigned long long
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 2e5 + 100;
int n, p, q, r, a[N], b[N], c[N];
ll f[55][55][55][55];
bool vis[N];
int P[N];
int p2,q2,r2;
inline void deal_baoli() {
memset(f, 0x3f, sizeof(f));
f[0][0][0][0] = 0; //
ll rel = 1e18;
//for(rint i=1;i<=n;++i)b[i]=min(b[i],c[i]),a[i]=min(a[i],b[i]);
for (rint i = 1; i <= n; ++i) {
for (rint j = 0; j <= i; ++j) {
for (rint k = 0; k <= i; ++k) {
for (rint l = 0; l <= i; ++l) {
if (j)
f[i][j][k][l] = min(f[i][j][k][l], f[i - 1][j - 1][k][l] + a[i]);
if (k)
f[i][j][k][l] = min(f[i][j][k][l], f[i - 1][j][k - 1][l] + b[i]);
if (l)
f[i][j][k][l] = min(f[i][j][k][l], f[i - 1][j][k][l - 1] + c[i]);
f[i][j][k][l]=min(f[i][j][k][l],f[i-1][j][k][l]);
}
}
}
for(rint j=r2;j<=n;++j)
{
for(rint k=max(0,q2-j);k<=n;++k)
{
for(rint l=max(0,p2-k-j);l<=n;++l)
rel=min(rel,f[i][l][k][j]);
}
}
}
chu("%lld", rel);
}
int main() {
//freopen("1.in", "r", stdin);
//freopen("angrybird.out", "w", stdout);
freopen("angrybird.in","r",stdin);
freopen("angrybird.out","w",stdout);
n = re(), p = re(), q = re(), r = re();
p2=p,q2=q,r2=r;
for (rint i = 1; i <= n; ++i) a[i] = re();
for (rint i = 1; i <= n; ++i) b[i] = re();
for (rint i = 1; i <= n; ++i) c[i] = re();
int q1 = q-r, p1 = p-max(q,r);
p = p1;
q = q1;
if (!p && !q && !r) {
chu("0");
return 0;
}
if (p && !q && !r) {
ll sun = 0;
for (rint i = 1; i <= n; ++i) a[i] = min({ a[i], b[i], c[i] });
sort(a + 1, a + 1 + n);
for (rint i = 1; i <= p; ++i) sun += a[i];
chu("%lld", sun);
return 0;
}
if (n <= 50) {
//chu("df\n");
deal_baoli();
return 0;
}
ll rel = 0;
for (rint i = 1; i <= n; ++i) P[i] = i;
sort(P + 1, P + 1 + n, [&](int x, int y) { return a[x] < a[y]; });
for (rint i = 1; i <= n; ++i)
if (p > 0)
rel += a[P[i]], vis[P[i]] = 1, p--;
for (rint i = 1; i <= n; ++i) P[i] = i;
sort(P + 1, P + 1 + n, [&](int x, int y) { return b[x] < b[y]; });
for (rint i = 1; i <= n; ++i) {
if (!vis[i] && q) {
rel += b[P[i]], vis[P[i]] = 1;
q--;
}
}
for (rint i = 1; i <= n; ++i) P[i] = i;
sort(P + 1, P + 1 + n, [&](int x, int y) { return c[x] < c[y]; });
for (rint i = 1; i <= n; ++i) {
if (!vis[i] && r) {
rel += c[P[i]], vis[P[i]] = 1;
r--;
}
}
chu("%lld", rel);
return 0;
}
/*
3 2 1 1
1 2 5
10 6 6
2 8 8
*/
正解
考虑对问题进行一下简单转化,令\(q=q-r,p=p-max(q,r),bi=min(bi,ci),ci=min(ci,bi)\),首先对于pqr的是从“至少”变成“至多”,为什么?因为考虑在贪心情况下如果2星选择大于q-r个,那么首先3星必须选r个,从p里面扔掉一些肯定合法而且会花费变小。后面的是在干嘛?考虑可反悔贪心进行的是要求严格选择这些价值,但是和题目要求是不符合的,因为1星选择了k个,完全可以等价成3星(如果3星反而更便宜,那就完全没有必要选1星了),所以先传递一下一定不劣的选择。
然后就是维护7个堆表示选a反悔成b,选a反悔成c,选b反悔成a,选b反悔成c,选c,选b,选a。(不用反悔c,可能就是用费用流模型解释的东西吧,已经是在最优决策下选择的a和b,所以任意的c再去变成ab的一定不会更优)
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define chu printf
#define ll long long
#define ull unsigned long long
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 2e5 + 100;
int n, p, q, r, a[N], b[N], c[N];
int vis[N];
priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > qbc, qab, qac, qba, qa, qb,
qc;
ll ans;
inline void make_1star(int x) {
vis[x] = 1;
qab.push(make_pair(b[x] - a[x], x));
qac.push(make_pair(c[x] - a[x], x));
}
inline void make_2star(int x) {
vis[x] = 2;
qba.push(make_pair(a[x] - b[x], x));
qbc.push(make_pair(c[x] - b[x], x));
}
inline void make_3star(int x) { vis[x] = 3; }
inline ll getc_fromb() {
while (!qbc.empty() && vis[qbc.top().second] != 2) qbc.pop();
return qbc.empty() ? 1e18 : qbc.top().first;
}
inline ll getc_froma() {
while (!qac.empty() && vis[qac.top().second] != 1) qac.pop();
return qac.empty() ? 1e18 : qac.top().first;
}
inline ll getb_froma() {
while (!qab.empty() && vis[qab.top().second] != 1) qab.pop();
return qab.empty() ? 1e18 : qab.top().first;
}
inline ll geta_fromb() {
while (!qba.empty() && vis[qba.top().second] != 2) qba.pop();
return qba.empty() ? 1e18 : qba.top().first;
}
inline ll getc() //有标记就不能拿,为啥?好吧,确实
{
while (!qc.empty() && vis[qc.top().second]) qc.pop();
return qc.empty() ? 1e18 : qc.top().first;
}
inline ll getb() {
while (!qb.empty() && vis[qb.top().second]) qb.pop();
return qb.empty() ? 1e18 : qb.top().first;
}
inline ll geta() {
while (!qa.empty() && vis[qa.top().second]) qa.pop();
return qa.empty() ? 1e18 : qa.top().first;
}
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("angrybird.in", "r", stdin);
freopen("angrybird.out", "w", stdout);
n = re(), p = re(), q = re(), r = re();
int q_ = q - r;
int p_ = p - max(q, r);
q = q_;
p = p_;
for (rint i = 1; i <= n; ++i) a[i] = re();
for (rint i = 1; i <= n; ++i) b[i] = re();
for (rint i = 1; i <= n; ++i) c[i] = re();
for (int i = 1; i <= n; i++) b[i] = min(b[i], c[i]);
for (int i = 1; i <= n; i++) a[i] = min(a[i], b[i]);
for (rint i = 1; i <= n; ++i) {
qa.push(make_pair(a[i], i));
qb.push(make_pair(b[i], i));
qc.push(make_pair(c[i], i));
}
for (rint i = 1; i <= p; ++i) //先选择a,就是拿p个小的
{
make_1star(qa.top().second);
ans += qa.top().first;
qa.pop();
}
for (rint i = 1; i <= q; ++i) {
//考虑可以从b直接拿
ll x = getb();
ll y = getb_froma() + geta();
if (x < y) {
int tmp = qb.top().second;
qb.pop();
make_2star(tmp);
ans += x;
} else {
int tmp = qab.top().second;
qab.pop();
make_2star(tmp);
tmp = qa.top().second;
qa.pop();
make_1star(tmp);
ans += y;
}
}
for (rint i = 1; i <= r; ++i) {
ll A = getc();
ll B = getc_fromb() + getb(), C = getc_fromb() + getb_froma() + geta();
ll D = getc_froma() + geta(), E = getc_froma() + geta_fromb() + getb();
if (A < B && A < C && A < D && A < E) {
int tmp = qc.top().second;
qc.pop();
make_3star(tmp);
ans += A;
} else if (B < C && B < D && B < E) {
int tmp = qbc.top().second;
qbc.pop();
make_3star(tmp);
tmp = qb.top().second;
qb.pop();
make_2star(tmp);
ans += B;
} else if (C < D && C < E) {
int tmp = qbc.top().second;
qbc.pop();
make_3star(tmp);
tmp = qab.top().second;
qab.pop();
make_2star(tmp);
tmp = qa.top().second;
qa.pop();
make_1star(tmp);
ans += C;
} else if (D < E) {
int tmp = qac.top().second;
qac.pop();
make_3star(tmp);
tmp = qa.top().second;
qa.pop();
make_1star(tmp);
ans += D;
} else {
int tmp = qac.top().second;
qac.pop();
make_3star(tmp);
tmp = qba.top().second;
qba.pop();
make_1star(tmp);
tmp = qb.top().second;
qb.pop();
make_2star(tmp);
ans += E;
}
}
chu("%lld", ans);
return 0;
}
/*
防止玛n*100行+
函数:
【1】把i选成3星的-->2和1的反悔
标记?
(选的时候如果vis[top]!=说明目前物品不在这,就pop,直到可以)
【2】从7个堆里面取出来最优的答案,返回
10个函数?
what...
*/