2023.11.01 考试总结&题解
T1 copy
标签:杂项 \(C\)
这道题我们可以看到 \(k\) 非常的小,所以说我们可以尝试 正难则反,对于每一个位置,我们将操作反着做一遍,可以得到该点再一开始的时候的位置,时间复杂度:\(O(nk)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 2e5 + 8;
int k,m,n;
char s[NN];
struct Operation{
ll l,r,x;
ll operator * (ll pos){
ll len = r - l + 1;
if(pos <= x) return pos;
else if(x+1 <= pos && pos <= x + len) return l + (pos - x - 1);
else return pos - len;
}
}p[NN];
ll solve(ll x){
for(int i = n; i >= 1; --i){
x = p[i] * x;
}
return x;
}
int main(){
freopen("copy.in","r",stdin);
freopen("copy.out","w",stdout);
scanf("%d%d",&k,&m);
scanf("%s",s+1);
scanf("%d",&n);
for(int i = 1; i <= n; ++i){
scanf("%d%d%d",&p[i].l,&p[i].r,&p[i].x);
++p[i].l;p[i].r;p[i].x;
}
for(int i = 1; i <= k; ++i){
putchar(s[solve(i)]);
}
puts("");
}
T2 hard
标签:DP \(B\)
我们可以发现,我们对于每一种方案,都可以转化为最多只有两个操作覆盖再一个点上,并且赋值操作在翻转操作前面
我们记 \(f_{i,0/1/2/3}\) 表示前 \(i\) 个球,在该点 无操作/赋值/翻转/先赋值再翻转。
式子如下:
#include<bits/stdc++.h>
using namespace std;
const int NN = 1e6 + 8,INF = 0x3f3f3f3f;
int n;
bool a[NN],b[NN];
int f[NN][4];
int main(){
freopen("hard3.in","r",stdin);
freopen("hard.out","w",stdout);
scanf("%d",&n);
for(int i = 1; i <= n; ++i) scanf("%1d",&a[i]);
for(int i = 1; i <= n; ++i) scanf("%1d",&b[i]);
f[0][1] = f[0][2] = f[0][3] = INF;
for(int i = 1; i <= n; ++i){
/*i上没有任何操作*/
if(a[i] != b[i]) f[i][0] = INF;
else f[i][0] = min(min(f[i-1][0], f[i-1][1]), min(f[i-1][2], f[i-1][3]));
/*i上有翻转操作*/
if(a[i] == b[i]) f[i][1] = INF;
else f[i][1] = min(min(f[i-1][0]+1, f[i-1][1]), min(f[i-1][2]+1, f[i-1][3]));
/*i上有赋值操作*/
f[i][2] = min(f[i-1][0]+1, f[i-1][1]+1);
if(b[i-1] == b[i]) f[i][2] = min(f[i][2], min(f[i-1][2], f[i-1][3]+1));
else f[i][2] = min(f[i][2],min(f[i-1][2]+1, f[i-1][3]));
/*i上有赋值翻转操作*/
f[i][3] = min(f[i-1][0]+2,f[i-1][1]+2);
if(b[i-1] != b[i]) f[i][3] = min(f[i][3],min(f[i-1][2]+1,f[i-1][3]+2));
else f[i][3] = min(f[i][3],min(f[i-1][2]+2,f[i-1][3]));
}
printf("%d",min(min(f[n][0],f[n][1]),min(f[n][2],f[n][3])));
}
T3 talk
原题:P8162 [JOI 2022 Final] 让我们赢得选举 (Let's Win the Election)
考虑显然我们不知道有多少个支持州,而此问题显然关于支持州的个数有凸性,显然可以先套一个 三分答案/二分斜率上去。
然后我们的的贪心显然是 \(b\) 从小到大选,但显然是错误的。
如果说不选当前的 \(b_i\) 的话,那么一定是因为 \(a_i\) 的代价更小,更该选,所以说不选 \(b\) 的话,一定选 \(a\)。
于是我们就可以设 \(f_{i,j}\) 表示选了 \(b\) 从小到大的前 \(i\) 个州,其中选了 \(j\) 个支持州的最小花费时间。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
const int NN = 1e3 + 8;
int n,m;
struct State{
ld a,b;
bool operator < (const State &x)const{
return b < x.b;
}
}s[NN];
ld res[NN];
ld f[NN][NN];
ld sum[NN];
ld check(int k){
if(res[k] != 1e18) return res[k];
for(int i = 0; i <= n; ++i){
for(int j = 0; j <= n; ++j) f[i][j] = 1e18;
}
f[0][0] = 0.0;
for(int i = 1; i <= n; ++i)
for(int j = 0; j <= k; ++j)
{
f[i][j] = min(f[i][j],f[i-1][j] + s[i].a / (k + 1));
if(j && s[i].b != 1e18) f[i][j] = min(f[i][j],f[i-1][j-1] + s[i].b / j);
}
for(int i = k; i <= m; ++i)
res[k] = min(res[k],f[i][k] + sum[i] / (ld)(k + 1));
return res[k];
}
bool cmp(State &x,State &y){return x.a < y.a;}
int main(){
freopen("talk.in","r",stdin);
freopen("talk.out","w",stdout);
scanf("%d%d",&n,&m);
int cntb = 0;
res[0] = 1e18;
for(int i = 1; i <= n; ++i){
scanf("%Lf%Lf",&s[i].a,&s[i].b);
cntb += s[i].b != -1;
if(s[i].b == -1) s[i].b = 1e18;
res[i] = 1e18;
}
sort(s+1,s+1+n);
for(int i = 0; i <= m; ++i){
sort(s+i+1,s+1+n,cmp);
for(int j = i+1; j <= m; ++j)
sum[i] = sum[i] + s[j].a;
sort(s+i+1,s+1+n);
}
cntb = min(cntb,m);
int l = 0,r = cntb;
ld ans = 1e18;
while(l <= r){
int lt = (l*2+r) / 3,rt = (l+r*2) / 3;
ld lans = check(lt),rans = check(rt);
ans = min(ans,min(lans,rans));
if(rans > lans) r = rt-1;
else l = lt+1;
}
printf("%.15Lf",ans);
}
T4 tree
改编原题:P5439 【XR-2】永恒
标签:DS \(B\) | 图论 \(B^+\) | 数学 \(C\)
遇到这么多个 \(\sum\),当然选择拆贡献分别计算。记点 \(u\) 在 \(T_1\)上的子树大小为 \(siz_u\),深度为 \(dep_u\)。
考虑 \(f(u,v)\) 被计入答案的次数:
若 \(u\) 和 \(v\) 没有祖先关系,答案显然为 \(siz_u\times siz_v \times f(u,v)\)。
否则, 不妨令 \(u\) 为 \(v\) 的祖先,则答案为 \((n-siz_{jump(v,u)})\times siz_v \times f(u,v)\)。
\(jump(v,u)\) 表示 \(v\) 所在的 \(u\) 的儿子的子树
对于第一类贡献,考虑在 \(T_2\) 上插入所有点,并查询。
对于第二类贡献,考虑在 dfs \(T_1\) 过程中逐个插入,并查询。
接下来,只需要关注如何插入和查询点 。对于插入,在 \(T_2\) 上把根节点到 \(u\) 的路径上的点加权值。
对于查询,在 \(T_2\) 上对根节点到 \(u\) 的路径上的点权求和即可。
可以证明上述操作均正确。使用树剖进行链加链求和,复杂度 \(O(n\log^2 n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
const int NN = 1e6 + 8,MM = 1e6 + 8;
int n,m;
int a[NN];
ll ans;
ll inv2 = (MOD+1) / 2;
struct Seg{
int l,r;
ll num,tag;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define num(x) tree[x].num
#define tag(x) tree[x].tag
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
}tree[MM << 2];
void addlz(int x,ll num){
num(x) += (r(x)-l(x)+1) * num % MOD;
tag(x) += num;
if(num(x) >= MOD) num(x) -= MOD;
if(tag(x) >= MOD) tag(x) -= MOD;
}
void pushdown(int x){
addlz(ls(x),tag(x));
addlz(rs(x),tag(x));
tag(x) = 0;
}
void pushup(int x){
num(x) = num(ls(x)) + num(rs(x));
}
void build(int x,int l,int r){
l(x) = l; r(x) = r;
if(l == r) return;
int mid = (l + r) / 2;
build(ls(x),l,mid);
build(rs(x),mid+1,r);
}
void modify(int x,int l,int r,ll num){
if(l <= l(x) && r(x) <= r){
addlz(x,num);
return;
}
int mid = (l(x) + r(x)) / 2;
pushdown(x);
if(l <= mid) modify(ls(x),l,r,num);
if(mid + 1 <= r) modify(rs(x),l,r,num);
pushup(x);
}
ll query(int x,int l,int r){
if(l <= l(x) && r(x) <= r) return num(x);
ll mid = (l(x) + r(x)) / 2,ans = 0;
pushdown(x);
if(l <= mid) ans += query(ls(x),l,r);
if(mid + 1 <= r) ans += query(rs(x),l,r);
return ans % MOD;
}
struct Tree{
struct Edge{
int to,next;
}edge[MM];
int head[MM],cnt,root;
int siz[NN];
void init(){
memset(head,-1,sizeof(head));
cnt = 1;
}
void add_edge(int u,int v){
edge[++cnt] = {v,head[u]};
head[u] = cnt;
}
}T1,T2;
int fa[NN],dep[NN],son[NN],top[NN];
int dfn[NN],timet,R[NN];
ll query_link(int u){
ll res = 0;
while(top[u]){
res = (res + query(1,dfn[top[u]],dfn[u])) % MOD;
u = fa[top[u]];
}
return res;
}
void add_link(int u,int num){
while(top[u]){
modify(1,dfn[top[u]],dfn[u],num);
u = fa[top[u]];
}
return;
}
void dfs1(int u){
T1.siz[u] = 1;
for(int i = T1.head[u]; i != -1; i = T1.edge[i].next){
int v = T1.edge[i].to;
dfs1(v);
T1.siz[u] += T1.siz[v];
}
}
void dfs2(int u){
ans = (ans + 1ll * query_link(a[u]) * T1.siz[u] % MOD) % MOD;
for(int i = T1.head[u]; i != -1; i = T1.edge[i].next){
int v = T1.edge[i].to;
add_link(a[u],n-T1.siz[u]-T1.siz[v]);
dfs2(v);
add_link(a[u],T1.siz[u]+T1.siz[v]-n);
}
}
ll s[NN],t[NN];
void dfs3(int u,int fa){
T2.siz[u] = 1;
::fa[u] = fa;dep[u] = dep[fa]+1;
for(int i = T2.head[u]; i != -1; i = T2.edge[i].next){
int v = T2.edge[i].to;
dfs3(v,u);
T2.siz[u] += T2.siz[v];
s[u] = (s[u] + s[v]) % MOD;//????????????
if(T2.siz[v] > T2.siz[son[u]]) son[u] = v;
}
}
void dfs4(int u,int top){
dfn[u] = ++timet; ::top[u] = top;
if(son[u]) dfs4(son[u],top);
for(int i = T2.head[u]; i != -1; i = T2.edge[i].next){
int v = T2.edge[i].to;
if(v == son[u]) continue;
dfs4(v,v);
}
t[dfn[u]] = (t[dfn[u]] + s[u]) % MOD;
t[timet+1] = (t[timet+1] + MOD - s[u]) % MOD;
}
int main(){
T1.init();T2.init();
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d%d",&n,&m);
build(1,1,m);
for(int i = 1,x; i <= n; ++i){
scanf("%d",&x);
if(x == 0) T1.root = i;
else T1.add_edge(x,i);
}
dfs1(T1.root);
for(int i = 1,x; i <= m; ++i){
scanf("%d",&x);
if(x == 0) T2.root = i;
else T2.add_edge(x,i);
}
for(int i = 1; i <= n; ++i){
scanf("%d",&a[i]);
s[a[i]] += T1.siz[i];
}
dfs3(T2.root,0);dfs4(T2.root,T2.root);
// for(int i=1;i<=100;++i)cerr<<dep[i]<<" ";
for(int i = 1;i <= m; ++i)
t[i] = (t[i] + t[i-1]) % MOD;
for(int i = 1; i <= n; ++i)
ans = (ans + (t[dfn[a[i]]] - 1ll * dep[a[i]] * T1.siz[i] % MOD) * T1.siz[i]) % MOD;
ans = ans * inv2 % MOD;
dfs2(T1.root);
printf("%lld",(ans % MOD + MOD) % MOD);
}
本文来自博客园,作者:ricky_lin,转载请注明原文链接:https://www.cnblogs.com/rickylin/p/test_2023-11-01.html

浙公网安备 33010602011771号