XSYOJ-NOI2022模拟测试赛(八)
XSYOJ-NOI2022模拟测试赛(八)
英雄史观
题目
题目背景
EAM:
我现在在想英雄史观和群众史观的问题
EAM:
(当时并非绝对的英雄史观和绝对的群众史观)
EAM:
因为这两者前者恶毒后者愚蠢
风影剑醉:
仔细说说
EAM:
但是如果辩证的来看你更偏向哪一方?
EAM:
英雄史观唯心,但我不想弄得那么绝对
风影剑醉:
我个人肯定是支持英雄史观吧
EAM:
我知道马克思主义哲学说人民群众是社会历史主体,是社会变革的决定力量
风影剑醉:
我也知道
EAM:
英雄造时势
EAM:
但是吧
风影剑醉:
我就是因为 这个政治书跟我的好多理解不太一样,我才不选政治的
EAM:
EAM:
我理解他的科学依据
EAM:
并且很有道理
题目描述
现在 cyc 有 \(N\) 个英雄,他们构成了一个历史关系,这个历史关系恰好满足是一棵树。这棵树的每条边都有历史相关值 \(w\)。
任意两个英雄 \(i\) 和 \(j\) 的历史相关度 \(dist(i,j)\) 就是他们之间的历史关系相关值之和。
现在,rsx给定了 \(M\) 个关键英雄,cyc 要选择其中的 \(K\)( \(K\) 可以是一个你选择的值),满足 \(K\times C+\sum_{i=1}^M dist(a_i,b_i)\),其中 \(b_i\) 是选择的 \(K\) 个英雄里和第 \(i\) 个关键英雄 \(a_i\) 相关最密切的是 \(b_i\)
输入格式
第一行三个正整数 \(n,m,C\) 表示树的大小,关键点个数以及选择的代价。
接下来 \(n-1\) 行,每行三个整数 \(u,v,w\) 表示树上的一条边 \(u,v\) 历史相关值为 \(w\)。
接下来一行 \(m\) 个数字,表示关键点。
输出格式
一行一个非负整数表示答案。
输入输出样例
post.in
5 3 3
1 2 1
2 3 1
3 4 1
3 5 1
1 4 5
post.out
7
数据规模与约定
对于全部的数据 \(m,n\le 3\times 10^3,1\le W,C\le 10^9\)
AC代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
// typedef long long ll;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
#define repg(i,u) for(int i=head[u];~i;i=edge[i].nxt)
#define lowbit(x) (x&(-x))
#define ls (o<<1)
#define rs (o<<1|1)
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define db double
#define endl '\n'
#define push_back emplace_back
inline int read(){
int num=0,f=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=0;
ch=getchar();
}
while(isdigit(ch)){
num=num*10+ch-'0';
ch=getchar();
}
return f?num:-num;
}
const int MAXN=3010;
const int INF=1e17;
int n,m,C;
int a[MAXN];
vector<pii> G[MAXN];
int f[MAXN][MAXN],g[MAXN][MAXN];
// f_{u,i}: u 子树内尚未匹配完,剩余 j 个点
// g_{u,v}: u 子树内匹配完了,离 u 最近的点是 v
int dep[MAXN];
int siz[MAXN];
vector<int> pts[MAXN];
void dfs(int u,int fa,int fval){
siz[u]=a[u];
for(auto it: G[u]){
int v=it.fi,w=it.se;
if(v==fa)continue;
dep[v]=dep[u]+w;
dfs(v,u,w);
siz[u]+=siz[v];
}
static int tmpf[MAXN],tmpg[MAXN];
rep(j,0,siz[u])tmpf[j]=INF;
f[u][0]=0;
int nowsiz=0;
for(auto it: G[u]){
int v=it.fi;
if(v==fa)continue;
rep(j,0,nowsiz){
rep(k,0,siz[v])tmpf[j+k]=min(tmpf[j+k],f[u][j]+f[v][k]);
}
for(auto x: pts[v])g[u][x]=INF;
rep(j,0,nowsiz){
for(auto x: pts[v]){
int now=f[u][j]+g[v][x];
now+=j*(dep[x]-dep[u]);
g[u][x]=min(g[u][x],now);
}
}
for(auto x: pts[u])tmpg[x]=INF;
for(auto x: pts[u]){
rep(j,0,siz[v]){
int now=g[u][x]+f[v][j];
now+=j*(dep[x]-dep[u]);
tmpg[x]=min(tmpg[x],now);
}
}
for(auto x: pts[u])g[u][x]=tmpg[x];
for(auto x: pts[v])pts[u].push_back(x);
nowsiz+=siz[v];
rep(j,0,nowsiz){
f[u][j]=tmpf[j];
tmpf[j]=INF;
}
}
per(j,nowsiz+a[u],a[u])f[u][j]=f[u][j-a[u]];
per(j,a[u]-1,0)f[u][j]=INF;
pts[u].push_back(u);
g[u][u]=INF;
rep(j,0,nowsiz+a[u]){
int now=f[u][j];
now+=C;
g[u][u]=min(g[u][u],now);
}
for(auto x: pts[u])g[u][x]+=a[u]*(dep[x]-dep[u]);
for(auto x: pts[u])f[u][0]=min(f[u][0],g[u][x]);
rep(j,0,siz[u])f[u][j]+=j*fval;
}
void solve(){
n=read(),m=read(),C=read();
rep(i,1,n-1){
int u=read(),v=read(),w=read();
G[u].push_back(mp(v,w)),G[v].push_back(mp(u,w));
}
rep(i,1,m){
a[read()]++;
}
dfs(1,0,0);
printf("%lld\n",f[1][0]);
}
signed main(){
// freopen("post2.in","r",stdin);
// int T=read();
int T=1;
while(T--)solve();
}
我们设 \(f_{i,j}\) 表示 \(i\) 节点的子树,内还有 \(j\) 个点没有被匹配到选择的点上。当前的代价和(要计算这 \(i\) 个点到 \(j\) 的的路径长度)。
然后还需要设 \(g[i][j]\) 表示 \(i\) 子树的所有点已经被匹配,然后现在树外边来的点只能到 \(j\) 的最小代价。
我们考虑怎么转移。。首先 \(f\) 的转移就是个路径背包。然后考虑关于 \(g\) 的转移。
首先加入一棵子树,我们要算如果这个大树里的点匹配到子树里的点的最优。
然后子树里的点匹配到大树里的点的最优秀。
然后答案就是 \(F[1][0]\)
群众史观
题目
题目背景
谁是最伟大的人?
他说是人民,人民说是他。如果没有他,就没有这句话。
题目描述
你有一个矩阵。
求他的行列式。
答案对于 \(998244353\) 取模
输入格式
一行一共两个正整数 \(N,C\)
输出格式
一行一个非负整数表示行列式的值。
输入输出样例
bigben.in
bigben.out
数据规模与约定
对于全部的数据 \(1\le C<998244353, 1\le N \le 10^{12}\)
AC代码
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::vector;
using std::copy;
using std::reverse;
using std::swap;
using std::array;
using std::cerr;
using std::function;
using std::map;
using std::set;
using std::pair;
using std::mt19937;
using std::make_pair;
using std::tuple;
using std::make_tuple;
using std::uniform_int_distribution;
namespace qwq {
mt19937 eng;
void init(int Seed) {
eng.seed(Seed);
return;
}
int rnd(int l = 1, int r = 1000000000) {
return uniform_int_distribution<int> (l, r)(eng);
}
}
template <typename T>
inline T min(const T &x, const T &y) {
return x < y ? x : y;
}
template<typename T>
inline T max(const T &x, const T &y) {
return x > y ? x : y;
}
template <typename T>
inline T read() {
T x = 0;
bool f = 0;
char ch = getchar();
while (!isdigit(ch)) {
f = ch == '-';
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
return f ? -x : x;
}
#define O(x) cerr << #x << " : " << x << '\n'
const double Pi = acos(-1);
const int MAXN = 262144, MOD = 998244353, inv2 = (MOD + 1) / 2;
auto Ksm = [] (int x, int y) -> int {
if (y < 0) {
y %= MOD - 1;
y += MOD - 1;
}
int ret = 1;
for (; y; y /= 2, x = (long long) x * x % MOD) {
if (y & 1) {
ret = (long long) ret * x % MOD;
}
}
return ret;
};
auto Mod = [] (int x) -> int {
if (x >= MOD) {
return x - MOD;
}
else if (x < 0) {
return x + MOD;
}
else {
return x;
}
};
inline int ls(int k) {
return k << 1;
}
inline int rs(int k) {
return k << 1 | 1;
}
const int MAXL = 3e7 + 10;
long long N, C, A;
int F[MAXL], G[MAXL], pri[MAXL], w[MAXL], mnp[MAXL], vf[MAXN];
bool vis[MAXL];
int SF(long long x) {
if (x < MAXL) {
return F[x];
}
int id = N / x;
if (vf[id]) {
return vf[id];
}
int ret = 0;
for (long long l = 2, r; l <= x; l = r + 1) {
r = x / (x / l);
ret = (ret + (r - l + 1) % MOD * SF(x / l)) % MOD;
}
return vf[id] = ((long long) ret * A + F[1]) % MOD;
}
int main() {
std::ios::sync_with_stdio(0);
cout << std::fixed << std::setprecision(8);
cin.tie(0);
cout.tie(0);
qwq::init(20050112);
cin >> N >> C;
if (C == 1) {
cout << (N <= 2);
return 0;
}
A = (long long) C * Ksm(MOD + 1 - C, -1) % MOD;
O(A);
G[1] = Ksm(MOD + 1 - C, -1);
for (int i = 2; i < MAXL; ++i) {
if (!vis[i]) {
pri[++*pri] = i;
mnp[i] = i;
w[i] = 1;
}
for (int j = 1; j <= *pri && pri[j] * i < MAXL; ++j) {
if (i % pri[j]) {
w[i * pri[j]] = w[i] + 1;
mnp[i * pri[j]] = mnp[i] * pri[j];
vis[i * pri[j]] = 1;
}
else {
vis[i * pri[j]] = 1;
w[i * pri[j]] = w[i];
mnp[i * pri[j]] = mnp[i];
break;
}
}
}
const int wp[] = {0, 2, 6, 30, 210, 2310, 30030, 510510, 9699690};
mnp[1] = 1;
for (int i = 2; i < MAXL; ++i) {
mnp[i] = mnp[i / mnp[i]] * wp[w[i]];
if (mnp[i] != i) {
G[i] = G[mnp[i]];
continue;
}
G[i] = G[1];
for (int j = 2; j * j <= i; ++j) {
if (i % j == 0) {
G[i] = Mod(G[i] + G[j]);
if (j * j != i) {
G[i] = Mod(G[i] + G[i / j]);
}
}
}
G[i] = (long long) G[i] * A % MOD;
}
for (int i = 1; i < MAXL; ++i) {
F[i] = Mod(G[i] + F[i - 1]);
}
int ANS = SF(N);
O(F[1]);
O(F[99]);
ANS = (long long) Ksm(MOD + 1 - C, N % (MOD - 1)) * ANS % MOD;
cout << ANS << '\n';
// cout << (-3 / 2);
return (0-0);
}
你发现,把矩阵写出来,比如咱写个 \(N=6\) 的矩阵。
然后你发现,把每一行减去下一行,
就变成了一个上海森堡矩阵,每一行的 \(i\) 个位置变成了 \(1-C\),然后后面位置自己有变化(大概)。然后对于每个行除以 \(C\)。令 \(v=\frac{1-C}{C}\)
然后,考虑上海森堡矩阵其实是可以 \(O(N^2)\) 求行列式的。
就考虑设 \(f_i\) 表示当前的答案。然后可以直接考虑枚举置换环 dp。因为这个行列式里的每个置换,考虑经过 \(i\) 的置换,一定是 \(i\to j \to j - 1 \cdots \to i+1\to i\) 的。然后就可以dp。
然后就是这道题的巨大做法了。
那么,我们其实有设 \(f_j\) 是做到 \(j\) 的答案。然后考虑 \(f_i=f_{i-1}+v\sum_{j\vert i\and j \not = i} f_j-f_{j-1}\),然后考虑,不妨整个差分完事。
令 \(g_j=f_j-f_{j-1}\)。然后,有 $g_i=v\sum_{j\vert i\and j \not = i}g_j $。
我们考虑怎么求整个东西,应该可以杜教筛。
具体咋做。
这个帖子。
会解答你的疑惑。
然后如何 \(O(n^{\frac{2}{3}})\) 预处理,可以选择代表元之后 \(O(\sqrt v)\) 的做一个代表元即可。
我们是最好的朋友
题目
题目背景
回忆了一下这些年的朋友圈
(以前还是真的能发)
曾经以为走不出的日子,现在都回不去了
曾经以为永远的朋友也大多走着走着就散了
但,愿往事如风,少年眉眼带笑,在新的陪伴鼓励下,清醒坚强,纤尘不染
题目描述
你有个算分治FFT复杂度的程序
def work(n)
return (p + 1) * 2 ^ p;
def solve(n)
if n = 1
return work(1);
return solve(ceil(n / 2)) + solve(floor(n / 2)) + work(n);
然后他想要对于一个 二进制数字 \(N\),查询 solve(N)
的答案。
然而这太简单了,你还准备添加一些修改操作
1 l r
将 \([l,r]\) 中的数字 \(0\to 1\), \(1\to 0\).2 l r
将 \([l,r]\) 中的数字都变成 \(0\)3 l r
将 \([l,r]\) 中的数字都变成 \(0\)4 l r
查询 \([l,r]\) 之间二进制数字的答案,右边是高位。
输入格式
一行一共两个正整数 \(N,M\)。
第二行一个长度为 \(N\) 的01串 \(S\)。
接下来 \(M\) 行,每行三个数字 \(op,l,r\) 如题意。
输出格式
每行对于每个 \(op=4\) 的询问输出你的答案。
输入输出样例
run1.in
5 20
00010
2 4 4
1 2 5
2 1 4
3 5 5
4 2 4
2 2 5
4 2 3
1 1 4
4 2 5
4 2 3
4 1 4
2 3 4
2 1 4
1 5 5
3 1 5
3 5 5
3 1 2
2 1 1
3 1 2
4 1 5
run1.out
0
0
75
19
235
run2.in
20 8
00001011001011101011
2 9 20
3 3 16
4 6 18
3 4 10
1 2 7
2 7 12
3 11 15
4 1 11
run2out
159739
103426
数据规模与约定
对于所有的数据,满足 \(1\le n,m\le 10^5,1\le l\le r\le n\)
AC代码
// 这是一份爆标做法 N log ^ 2 N
// 可以优化到 N log N 但是没必要
// 哦 可以使用 find_first 线段树上二分即可 优化到 O(N \log N)
#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::vector;
using std::copy;
using std::reverse;
using std::swap;
using std::array;
using std::cerr;
using std::function;
using std::map;
using std::set;
using std::pair;
using std::mt19937;
using std::make_pair;
using std::tuple;
using std::make_tuple;
using std::uniform_int_distribution;
namespace qwq {
mt19937 eng;
void init(int Seed) {
eng.seed(Seed);
return;
}
int rnd(int l = 1, int r = 1000000000) {
return uniform_int_distribution<int> (l, r)(eng);
}
}
template <typename T>
inline T min(const T &x, const T &y) {
return x < y ? x : y;
}
template<typename T>
inline T max(const T &x, const T &y) {
return x > y ? x : y;
}
template <typename T>
inline T read() {
T x = 0;
bool f = 0;
char ch = getchar();
while (!isdigit(ch)) {
f = ch == '-';
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
return f ? -x : x;
}
#define O(x) cerr << #x << " : " << x << '\n'
const double Pi = acos(-1);
const int MAXN = 1e5 + 10, MOD = 998244353, inv2 = (MOD + 1) / 2;
auto Ksm = [] (int x, int y) -> int {
if (y < 0) {
y %= MOD - 1;
y += MOD - 1;
}
int ret = 1;
for (; y; y /= 2, x = (long long) x * x % MOD) {
if (y & 1) {
ret = (long long) ret * x % MOD;
}
}
return ret;
};
auto Mod = [] (int x) -> int {
if (x >= MOD) {
return x - MOD;
}
else if (x < 0) {
return x + MOD;
}
else {
return x;
}
};
inline int ls(int k) {
return k << 1;
}
inline int rs(int k) {
return k << 1 | 1;
}
int N, M, p[MAXN], ip[MAXN], A[MAXN];
inline int get(int l, int r) {
return Mod(p[r + 1] - p[l]);
}
struct Segment_Tree {
int SZ, rev[MAXN * 4], cnt[MAXN * 4], val[MAXN * 4], tag[MAXN * 4];
inline void up(int u) {
cnt[u] = cnt[ls(u)] + cnt[rs(u)];
val[u] = Mod(val[ls(u)] + val[rs(u)]);
return;
}
inline void puttag(int nw, int rtag, int atag, int l, int r) {
if (atag != -1) {
tag[nw] = atag;
rev[nw] = 0;
val[nw] = atag ? get(l, r) : 0;
cnt[nw] = atag ? r - l + 1 : 0;
}
if (rtag) {
rev[nw] ^= 1;
val[nw] = Mod(get(l, r) - val[nw]);
cnt[nw] = r - l + 1 - cnt[nw];
}
}
inline void down(int nw, int l, int r) {
int mid = (l + r) / 2;
puttag(ls(nw), rev[nw], tag[nw], l, mid);
puttag(rs(nw), rev[nw], tag[nw], mid + 1, r);
rev[nw] = 0;
tag[nw] = -1;
return;
}
void build(int _N) {
function<void(int, int, int)> dfs = [&] (int nw, int l, int r) -> void {
tag[nw] = -1;
if (l == r) {
val[nw] = A[l] * p[l];
cnt[nw] = A[l];
return;
}
int mid = (l + r) / 2;
dfs(ls(nw), l, mid);
dfs(rs(nw), mid + 1, r);
return up(nw);
};
SZ = _N;
return dfs(1, 1, SZ);
}
void reverse(int ql, int qr) {
function<void(int, int, int)> dfs = [&] (int nw, int l, int r) -> void {
if (ql <= l && r <= qr) {
return puttag(nw, 1, -1, l, r);
}
int mid = (l + r) / 2;
down(nw, l, r);
if (ql <= mid) {
dfs(ls(nw), l, mid);
}
if (mid < qr) {
dfs(rs(nw), mid + 1, r);
}
return up(nw);
};
return dfs(1, 1, SZ);
}
void assign(int ql, int qr, int qx) {
function<void(int, int, int)> dfs = [&] (int nw, int l, int r) -> void {
if (ql <= l && r <= qr) {
return puttag(nw, 0, qx, l, r);
}
int mid = (l + r) / 2;
down(nw, l, r);
if (ql <= mid) {
dfs(ls(nw), l, mid);
}
if (mid < qr) {
dfs(rs(nw), mid + 1, r);
}
return up(nw);
};
return dfs(1, 1, SZ);
}
// 找到 [ql, qr] 之间的最靠右边的 1
int findr(int ql, int qr) {
if (ql > qr) {
return -1;
}
int tot = 0;
function<int(int, int, int)> dfs = [&] (int nw, int l, int r) -> int {
++tot;
if (ql <= l && r <= qr) {
// O(ql);
if (!cnt[nw]) {
return -1;
}
if (l == r) {
return l;
}
else {
down(nw, l, r);
int mid = (l + r) / 2;
if (cnt[rs(nw)]) {
return dfs(rs(nw), mid + 1, r);
}
else {
return dfs(ls(nw), l, mid);
}
}
}
down(nw, l, r);
int mid = (l + r) / 2;
if (mid < qr) {
int ret = dfs(rs(nw), mid + 1, r);
if (ret != -1) {
return ret;
}
}
if (ql <= mid) {
int ret = dfs(ls(nw), l, mid);
if (ret != -1) {
return ret;
}
}
return -1;
};
int ret = dfs(1, 1, SZ);
// O(tot);
return ret;
}
int query(int ql, int qr) {
function<int(int, int, int)> dfs = [&] (int nw, int l, int r) -> int {
if (ql <= l && r <= qr) {
return val[nw];
}
int mid = (l + r) / 2, ret = 0;
down(nw, l, r);
if (ql <= mid) {
ret = dfs(ls(nw), l, mid);
}
if (mid < qr) {
ret = Mod(ret + dfs(rs(nw), mid + 1, r));
}
return ret;
};
return dfs(1, 1, SZ);
}
} T;
int getf(int x) {
return (p[x] * (x + 1LL) + 1) % MOD;
}
int qry(int l, int r) {
int k = T.findr(l, r);
// 没有 1 存在
// O(k);
if (k == -1) {
return 0;
}
int m = T.findr(l, k - 1);
if (m == -1) {
// 2 的 幂次
k -= l;
return (k + 1LL) * (k + 2LL) / 2 % MOD * p[k] % MOD;
}
m -= l;
k -= l;
// 最高位贡献
int ANS = (k + 1LL) * (k + 2LL) / 2 % MOD * p[k] % MOD;
// 加到第一个
ANS = Mod(ANS + getf(k + 1));
// 后面除了最高位和次高位的贡献
ANS = (ANS + getf(k - m) * ((long long) T.query(l, r) * ip[l] % MOD - p[k] - p[m] + 2LL * MOD)) % MOD;
// 还有就是怎么加到次高位
// O(ANS);
// O(k);
// O(m);
ANS = (ANS + p[m] - 1 + p[k] * (k * 2LL - m + 3) % MOD * m % MOD * inv2) % MOD;
return ANS;
}
int main() {
// freopen("run.in", "r", stdin);
// freopen("run.out", "w", stdout);
// std::ios::sync_with_stdio(0);
// cout << std::fixed << std::setprecision(8);
// cin.tie(0);
// cout.tie(0);
qwq::init(20050112);
N = read <int> ();
// O(N);
M = read <int> ();
// O(M);
for (int i = 1; i <= N; ++i) {
A[i] = getchar() - '0';
}
p[0] = ip[0] = 1;
for (int i = 1; i < MAXN; ++i) {
p[i] = (long long) p[i - 1] * 2 % MOD;
ip[i] = (long long) ip[i - 1] * inv2 % MOD;
}
T.build(N);
for (int opt, l, r; M--; ) {
opt = read <int> ();
l = read <int> ();
r = read <int> ();
if (opt == 1) {
T.reverse(l, r);
}
else if (opt == 2) {
T.assign(l, r, 0);
}
else if (opt == 3) {
T.assign(l, r, 1);
}
else {
printf("%d\n", qry(l, r));
}
}
// cout << (-3 / 2);
return (0-0);
}
我们考虑,如果 \(solve(N)\) 变成 \(solve(N + 1)\) 他会怎么样呢,打个表发现,他多了两个 \(work(x)\)。
令 \(w(x)=(x+1)\times 2 ^ x\)
具体地说,是一个 \(w(0)\),还有一个 \(work(最高位1-最低位1)\)。
然后,我们就可以做,考虑 \(O(qn)\),每次暴力找出个下表最大和次大的一,然后剩下那些二进制数字的值,然后就可以计算 \(solve(x)\) 的值了。
加上修改,我们只要用线段树维护区间的值和1的个数即可。