9,10月做题
GZEZ:
1.Flipping Coins
有 nn 个硬币,初始时它们全部朝上。等概率随机地选一个 [1,n][1,n] 的排列 p_{1\dots n}p
1…n,依次进行 nn 次操作,第 ii 次操作如下:若第 ii 个硬币朝下,则不做任何事若第 ii 个硬币朝上,则翻ii 和第 p_ip i个硬币。结束后若有 kk 个硬币朝上,则你会得到 w^kwk津巴布韦币。
求出你能得到的津巴布韦币的期望,乘上 n!n! 并对 998244353998244353 取模输出
当时做的时候一开始打的暴力,后来仔细看了看Solution发现这种有影响性的问题可以建图连边然后再根据限制条件找环之类的最后统计ans的时候需要利用多项式NTT
2.Forbidden Value
有 nn 行指令,你需要依次执行这些指令。初始时,你有一个变量 x=0x=0,共有三种指令:set y v,令 x\gets yx←y,或用 vv 的代价删去这条指令;if y,如果 x=yx=y,则执行其范围内的指令,否则跳过其范围内的指令;end,用于声明上一个还未声明范围的 if 指令的范围。给一个数 ss,求用最少的代价使得没有一个时刻 x=sx=s。
一看题面想的就是栈模拟加搜索然后莫名其妙宝玲了,后来才知道是树形dp,对于两种操作,set if,把if当成父亲节点把if里的set's cost 提前弄到if上,对于开头的set建立一个cnt为0的if(不存在)的原点然后跑树形dp统计花费i最终到达j的方案
3.Not Equal Rectangle
让你构造一个矩阵使他满足任意一个子矩阵四个顶点的数不都相同
这挺玄幻的题我们考虑证明假设矩形的四个角分别在\(B_a,B_b,B_c,B_d\),因为每个小矩阵每行每列都没有两个相同的数,所以我们就需要满足如果四个角在四个不同的矩阵,那么这四个角的数当且仅当$a-d !=b-c $ 然后我们就取一个prime填满\(n*m\)即可
...
4.二龙戏珠
阿福是一个很喜欢玩珠子的 OIer ,现有 n 个红珠子和 An+B 个蓝珠子,从左往右放珠子,保证任何时候假如有 x 个红珠子,蓝珠子数量不超过 Ax+B 个,问有多少种摆放方法能摆完所有的珠子?
我们考虑对于蓝珠和红猪的增加相当于x,y轴向右上走,我们考虑计数问题从(0,0)走到(n,m)然后对于每个y不能超过a*x+b然后我们想用总额方案减去不合法的,我们统计第一次到达界限的上面一个然后在到达终点的方案数然后把每一个不合法的都加起来最后减回去就是最终答案了
5.脑袋砸核桃
阿福有 nn 个核桃,第 ii 个的“智慧度”为 a_ia但是由于他的脑袋不够硬,他最多先后砸开两个核桃定义两个核桃共同作用的“智慧度”为两个核桃“智慧度”的最大公约数。他希望知道总的“智慧度”为不同的 xx 的方案数。形式化的说,对于每次询问 xx,请求出有多少有序对 (i,j)(i,j) 满足 \gcd(a_i,a_j)=xgcd(ai,aj)=x。
20pts直接暴力预处理存进桶里面
40pts莫比乌斯反演存桶里面
100pts
神奇的gcd卷积
我们按照快速傅里叶变换(FFT)、快速莫比乌斯变换(FMT)解决卷积的思路来解决该问题——构造一种变换来满足卷积定理
\(f^(n)g^(n)=(f∘g)ˆ(n)\)
f^ 即对函数 f 进行该变换后得到的函数。
通过一些敏锐的直觉,我们能感受到 gcd 和枚举约数或者倍数有一些关系。
容易想到构造出一种变换,称之为“倍数和变换”:
\(f^(n)=∑n|df(d)\)
并根据莫比乌斯反演得到它的逆变换
\(f(n)=∑n|dμ(nd)f^(d)\)
这实际上是标准莫比乌斯反演的另一种形式,详见后文 [Further Thoughts](#Further Thoughts) )
这个变换对 gcd 卷积满足卷积定理,证明如下.
首先,写出 gcd 卷积
\(h(k)=(f∘g)(k)=∑i,j[gcd(i,j)=k]f(i.)g(j)\)
左右两边做倍数和变换:
\(h^(n)=∑n|k∑i,j[gcd(i ,j)=k]f(i)g(j)=∑i,j⎛⎝∑n|k[gcd(i,j)=k]⎞⎠f(i)g(j)=∑i,j[n|i][n|j]f(i)g(j)=∑n|if(i)∑n|jg(j)=f^(n)g^(n)\)
7.robo
8.birthday
对于操作 2,考虑用线段树 lazy-tag 实现区间修改。
区间幂不好区间修改,但考虑到每次最多调用 13 个数,可以下放到叶子节点时才释放 lazy
标记。
lazy 标记释放时,快速乘或暴力修改常数是巨大的。发现 b[i]值域在[0,v - 1],可以提前处
理好[0,v - 1]的幂的表格,用倍增实现。
data[i][0] = i 3 % v,data[i][j] = [data[i][j - 1]][j - 1]
对于操作 2,考虑用线段树 lazy-tag 实现区间修改。
区间幂不好区间修改,但考虑到每次最多调用 13 个数,可以下放到叶子节点时才释放 lazy
标记。
lazy 标记释放时,快速乘或暴力修改常数是巨大的。发现 b[i]值域在[0,v - 1],可以提前处
理好[0,v - 1]的幂的表格,用倍增实现。
data[i][0] = i 3 % v,data[i][j] = [data[i][j - 1]][j - 1]
9.expand
可惜没有special judge
2.1 对于30%的数据 所有菜市位置和小T所在的位置在一条水平直线上
考虑有部分菜市在小T右边,部分菜市在小T左边。
只有两种遍历情况:向左再向右、向右再向左
而在每个点处的、可行的最大体格是确定的,因此直接累加即可。
最终答案在两种情况中选取一个路径更短的或最短路径相同情况下体格和最大的。
2.2 对于s=0且n,m<=50的数据
当 时,问题转变为从一个点出发遍历 个点的最短路问题。
最朴素的想法应该是搜索,每次搜索下一步去哪个点。
但搜索的问题在于它考虑了经过每个点的顺序。
实际上经过顺序不会对答案产生影响、经过了哪些点才会有影响。
因此考虑用状压表示哪些点没去过,然后寻找一个没去过的点更新。
定义状态 表示还没去过 集合中的点,目前在点 的最短距离。
可使用bfs更新,复杂度 ,可解决 的情况。
2.3 对于s=0的数据
很容易可以发现, 的三维中, 在很多点是不会被更新的,因此这些点处出现了状态冗余。
因此只在 个点处定义 ,即状态改为 ,表示在第 个点处的最短路。
转移时需要利用两个点间的最短路。
最短路可通过 次bfs预处理得到。
复杂度
2.4 对于p=1的数据
只有一个目标点,因此只用考虑膨胀问题。
在每一个点都会重新膨胀,因此在每个点处的最大膨胀值其实是固定的。
在bfs的时候直接将每个点的膨胀值加入答案即可。
复杂度
2.5 对于100%的数据
将2.3中的bfs部分与2.4结合,
Noip摸你赛:
1.cruise
题面 :有一条河,然后有n条船没条船都有自己的船长和速度然后给出每条船船头到终点的距离长度让你求最后一条船的到达时间
Solution:
考虑整体,对于所有的船向前走,我们可以知道永远是前面的船在限制后面的船所以我们只需要要求出每条船的到达时间然后取个就可以了还有就是要求时间的时候我们需要加上船长因为你后面的船要走前面船的船长
2.Tarvelling
题面:
给定一个任意的无向图,问最少画几笔,使得所有的边被画过一次且仅一次
对于一张无向图,把每个连通块搞出来,因为每个连通块是独立的,我们要统计每个点的degree因为这个点必须要入度等于出度才能满足有进有出然后统计每个连通块的奇数度数点个数然后每次连两个奇数点会减少两个奇数点,如果形成了欧拉回路就加k/2条边我们只要将一个连通块变成欧拉图,搜一遍得到路径,再将我们人为添加的边删掉,剩下的就是这个连通块的"笔画"了。
3.Station
题面:给你一个 8×8 的矩阵,你需要遍历上面给定的某些格子。每次从一个点到另一个点的代价两个格子的切比雪夫距离的最大值。求最小总代价。
我们考虑dp求
首先假如说一维的一条线求切比雪夫那么我们是从中间向左右拓展然后我们二维也一样把四条边的贡献都求出来。我们需要递归子矩阵然后统计每条边上的点对矩阵的贡献
inline int calc(int x1,int y1,int x2,int y2,int i1,int j1,int i2,int j2)
{
int res = 0 ;
for(int i = i1 ; i <= i2 ; i ++ )
{
for(int j = j1 ; j <= j2 ; j ++ )
{
if(a[i][j] == 1) res += max(max(abs(i-x1),abs(i-x2)),max(abs(j-y1),abs(j-y2))) ;
}
}
return res;
}
这个东西其实来说他是在一条边上的'#'点对当前矩阵的剩下三条边的距离取max为什么成立类,可以说是假设不成立那就总会有子矩阵里的点递归回来覆盖当前答案
if(a[i][j] == 1) res += max(max(abs(i-x1),abs(i-x2)),max(abs(j-y1),abs(j-y2))) ;
3.trees
题面:
你现在有一个长度为n的序列,给你一个数m,让你从这n个数里选m个数的所有组合统计贡献(m个数里最大值)
我们先把这个序列排序然后统计m+1->n的数的贡献,在第i个点对于前i个点来说我们任意取m-1个点贡献都为ai所以答案就出来了
for(int i = m ; i <= n ; [[[i ++ )
ans += C(i-1,m-1) * a[i] ;
4.bridge
穿越了森林,前方有一座独木桥,连接着过往和未来(连接着上一题和下一题...)。这座桥无限长。小 Q 在独木桥上彷徨了。他知道,他只剩下了 N 秒的时间,每一秒的时间里,他会向左或向右移动一步。N 秒之后,小 Q 恰在桥上某一特定位置,且他每两次经过此位置的时间间隔不会超过 M秒。那么问题来了,这 N 秒的时间里,小 Q 的路线总共会有多少种可能的形式。
Solution:
若只考虑从最终到达的特定位置向单一方向出发,令 F(x, y)表示在一次出行内离开最终
位置 x 个单位时间时你恰好处于距最终位置 y 个单位距离的方案数。易得转移方程:【
F(1,1)=1,F(x,y)=F(x-1,y-1)+F(x-1,y+1). 预处理 F 数组,时间复杂度 \(O(M^2)\)
5.flowers
樱花雨一共持续了 N 秒。每一秒都会有 A 朵樱花飘落。小 Q 细心的记录了每一秒时间
后地上樱花的数目,并将其转换成了二进制。小 Q 想请你统计一下这些二进制数一共有多
少个 1。
Sloution:
30 pts : 直接模拟
60 pts : 高级模拟
100 pts : 超级模拟(bushi
依次统计二进制等差数列的每一位上分别共有多少个 1 。
对于这一等差数列 \(B+A,B+A*2,B+A*3,…,B+A*N\),显然第 i 项与第 2^K+i 项在第 K
位上的数字相同。
于是,可以将原数列按位拆成循环节统计。
另外,每一个循环节内连续的几个 0 或几个 1 的长度可以用 O(1)的时间复杂度求出。
这样便以 O(A)的时间复杂度和 O(1)的空间复杂度解决了这道题
9.5 GZEZ
A Or Plus Max
长度为\(2^N\)的序列A,编号\(0..2^N-10..2^N-1\)
对于每一个\(K(0<K<2^N)K(0<K<2^N)\),求 \(\max(A_i+A_j)(i\,\text{or}\, j\le K)max(A
i+Aj)(iorj≤K)\)
41pts:直接模拟\(n^2\)
for (int i = 0; i < m; i++)
{
for (int j = i + 1; j < m; j++)
{
int x = (i | j);
b[x] = max(b[x], a[i] + a[j]);
}
}
我们判断的这个i|j为(i,j)影响的最小值所以我们最后还要把(i,j)影响后移
for (int i = 1; i < m; i++)
{
for (int j = i + 1; j < m; j++)
{
b[j] = max(b[i], b[j]);
}
}
100pts:
求出所有的 i | j = k,询问时前缀max
这时候,我们不要再用数字的眼光看待 i, j, k 了。
我们把 i, j, k 每一个数都按照二进制理解为一个个集合,那么 i | j = k 就变成了 i ink, j in k 于是就变成了对于 k 的所有子集对应的值的最大值和次大值。 但是又不能枚举所有子集的最大值,所以搞一个 n 维的前缀max即可
#include <bits/stdc++.h>
using namespace std;
const int N = 19;
const int inf = 110;
struct Node
{
int m1, m2;
Node operator+(const Node &x)
{
Node y;
if (m1 > x.m1)
{
y.m1 = m1;
y.m2 = max(m2, x.m1);
}
else
{
y.m1 = x.m1;
y.m2 = max(m1, x.m2);
}
return y;
}
} a[1 << N];
int m, n;
int main()
{
scanf("%d",&n);
m = 1 << n;
for (int i = 0; i < m; i++)
scanf("%d", &a[i].m1), a[i].m2 = -inf;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if ((j >> i) & 1)
a[j] = a[j] + a[j ^ (1 << i)];
}
}
int ans = 0;
for (int j = 1; j < m; j++)
{
ans = max(ans, a[j].m1 + a[j].m2);
printf("%d\n",ans) ;
}
}
gugugugugugugugu
树:
题意:
给定一颗带权树,有q次操作来改变某一条边的权值,问在每一次的修改前后有多少对点的最短路径的权值gcd为1
考虑莫比乌斯反演,把边的权值质因数分解,去掉重复的,枚举d,把是d倍数的边取出来,用并查集统计,统计完答案再把边删去
Range XOR:
给定L,R,V,问在\((l,r)\)的区间范围内有多少个\((l,r)\)满足\(L<=l<=r<=R\)且\(l^(l+1)^..^r\)
对于 0≤k,我们定义 wk=0⊕1⊕⋯⊕(k−1)。
[]
我们想要计算满足 L≤i<j≤R+1 并且wi⊕wj=V的数对 (i,j)。
经过一点变换, 问题变成了寻找数对 (i,j) 满足 0≤i<A,0≤j<B,wi⊕wj=V.
把它们模4的结果进行分类,我们可以得到:
- w4x=x
- w4x+1=1
- w4x+2=x+1
- w4x+3=0
因此,我们只需确定 i 和 j 模4的余数,一共16种情况。但是比较难处理的只有 (0,0),(0,2),(2,0),(2,2) 四种情况,其余情况要么可以确定一个端点的位置,要么形如等差数列,均可快速求得。
这四种情况本质是一样的,给定区间 [l−1,r] ,从区间中抽取两个不相等的数 a,b ,求 a⊕b=V 的对数。
可以用数位dp解决,f[i][j][k] 表示还有 i 位,第一个数是否卡上界,第二个数是否卡上界的方案数。然后我们就做完了。
3.Simple Math 3
给你 \(A,B,C,D\),求有多少个正整数 i 满足:
闭区间 \([A+Bi,A+Ci]\) 中没有 D 的整数倍数。
容易证明,答案是有限的。
76pts: \(i=0\) 时 \(a+b*i=a+c*i\) 当随着 \(i\) 的增大两个值的差距越来越大当差值大于D的时候肯定存在D的整数倍数所以可求得边界值枚举i判断是否满足条件即可
100pts: 雷欧,求一个满足条件的柿子
雷欧带走即可
noip模拟赛34
def merge(a, b):
c=[]
while a.size() and b.size():
if a[0]<b[0]:
c. append(a[0])
a.pop_ front()
else:
c. append(b[0])
b.pop_ front()
return c+a+b
考虑一个平衡树做法,我们把变化的1区间split出来然后旋转一下维护平衡树的性质然后insert回去
QWQ:
区间推平很明显,是ODT(Old Driver Tree)
具体操作大概就是用set维护好多的桶
经典 set
#include<bits/stdc++.h>
using namespace std;
namespace fast_IO
{
#define FASTIO
#define IOSIZE 100000
char ibuf[IOSIZE], obuf[IOSIZE];
char *p1 = ibuf, *p2 = ibuf, *p3 = obuf;
#ifdef ONLINE_JUDGE
#define getchar() ((p1==p2)and(p2=(p1=ibuf)+fread(ibuf,1,IOSIZE,stdin),p1==p2)?(EOF):(*p1++))
#define putchar(x) ((p3==obuf+IOSIZE)&&(fwrite(obuf,p3-obuf,1,stdout),p3=obuf),*p3++=x)
#endif//fread in OJ, stdio in local
#define isdigit(ch) (ch>47&&ch<58)
#define isspace(ch) (ch<33)
template<typename T> inline T read()
{
T s = 0; int w = 1; char ch;
while(ch=getchar(),!isdigit(ch)and(ch!=EOF)) if(ch=='-') w=-1;
if(ch == EOF) return false;
while(isdigit(ch)) s=s*10+ch-48,ch=getchar();
return s*w;
}
template<typename T> inline bool read(T &s)
{
s = 0; int w = 1; char ch;
while(ch=getchar(),!isdigit(ch)and(ch!=EOF)) if(ch=='-') w=-1;
if(ch == EOF) return false;
while(isdigit(ch)) s=s*10+ch-48,ch=getchar();
return s*=w, true;
}
inline bool read(char &s)
{
while(s = getchar(), isspace(s));
return true;
}
inline bool read(char *s)
{
char ch;
while(ch=getchar(),isspace(ch));
if(ch == EOF) return false;
while(!isspace(ch)) *s++ = ch, ch = getchar();
*s = '\000'; return true;
}
template<typename T> inline void print(T x)
{
if(x < 0) putchar('-'), x = -x;
if(x > 9) print(x/10);
putchar(x%10+48);
}
inline void print(char x){ putchar(x); }
inline void print(char *x){ while(*x) putchar(*x++); }
inline void print(const char *x)
{ for(int i = 0; x[i]; i++) putchar(x[i]); }
#ifdef _GLIBCXX_STRING
inline bool read(std::string& s)
{
s = ""; char ch;
while(ch=getchar(),isspace(ch));
if(ch == EOF) return false;
while(!isspace(ch)) s += ch, ch = getchar();
return true;
}
inline void print(std::string x)
{
for(int i = 0, n = x.size(); i < n; i++)
putchar(x[i]);
}
#endif//string
template<typename T, typename... T1> inline int read(T& a, T1&... other){ return read(a)+read(other...); }
template<typename T, typename... T1> inline void print(T a, T1... other){ print(a); print(other...); }
struct Fast_IO{
~Fast_IO(){ fwrite(obuf, p3-obuf, 1, stdout); }
}io;
template<typename T> Fast_IO& operator >> (Fast_IO &io, T &b){ return read(b), io; }
template<typename T> Fast_IO& operator << (Fast_IO &io, T b){ return print(b), io; }
#define cout io
#define cin io
#define endl '\n'
}
using namespace fast_IO;
#define ll long long
const int MAXN = 1e6 ;
ll a[MAXN] ;
struct node{
int l ;
int r ;
mutable int v ;
node(ll l , ll r = 0 , ll v = 0) : l(l) , r(r) , v(v) {}
bool operator < (const node &a) const{
return l < a.l ;
}
};
set<node> s ;
set<node>::iterator split(int pos)
{
set<node>::iterator it = s.lower_bound(node(pos)) ;
if(it != s.end() && it -> l == pos) return it ;
it -- ;
if(it -> r < pos) return s.end() ;
ll l = it -> l ;
ll r = it -> r ;
ll v = it -> v ;
s.erase(it) ;
s.insert(node(l,pos-1,v)) ;
return s.insert(node(pos,r,v)).first ;
}
inline void assign(ll l , ll r ,ll x)
{
set<node>::iterator itr = split(r + 1) , itl = split(l) ;
s.erase(itl,itr) ;
s.insert(node(l,r,x)) ;
}
int G[MAXN];
inline bool calc(ll l , ll r , ll x)
{
set<node>::iterator itr = split(r+1) , itl = split(l) ;
bool flag = 1 ;
for(set<node>::iterator it = itl ; it != itr ; it ++ )
{
int g = it->v ;
G[g] += (it -> r - it -> l + 1) ;
if(G[g] > x) flag = 0 ;
}
for(set<node>::iterator it = itl ; it != itr ; it ++ )
{
int g = it->v ;
G[g] -= (it -> r - it -> l + 1) ;
}
return flag ;
}
/*
6
1 1 4 5 1 4
4
2 1 5 2
2 1 6 3
1 5 6 1
2 1 6 3
lal
*/
ll m , n ;
int main()
{
cin >> n ;
for(int i = 1 ; i <= n ; i ++ )
{
cin >> a[i] ;
s.insert(node(i,i,a[i])) ;
}
cin >> m ;
for(int i = 1 ; i <= m ; i ++ )
{
int opt ; int l , r ; int x ;
cin >> opt ;
cin >> l >> r >> x;
if(opt == 1)
{
assign(l , r , x) ;
}
else
{
bool flag = calc(l , r , x) ;
if(flag == false) cout << "laffey" ;
else cout << "ayanami" ;
cout << endl ;
}
}
}
Problem B: Cigar Box
首先我们不妨先假设我们已经得到了一个操作序列。
因此,我们可以定义对某一个元素的最后一次移动,我们称它为该元素的关键操作。
我们不妨假设我们已经对某两个元素完成了关键操作。
则在当前序列中不在这两个元素之间的元素不能够成为这两个元素之间的元素,换句话说这两个元素间的元素若在目标序列中也在这两个元素之间则他们必然已经完成了他们的关键操作,或者他们完全不用操作。
因此我们得到了结论一:完成关键操作的元素在目标序列中是连续的。
我们考虑那些完全不用操作的元素可能在哪些位置。
我们不妨考虑第一个完成向左的关键操作的元素,则在目标序列中在该元素左边的元素必然需要操作。
第一个完成向右的关键操作的元素同理。
则最后我们发现在第一个完成向左的关键操作的元素和第一个向右的之间的元素是不用操作的。则在目标序列中,这些元素相对位置和原序列中一样,即这些元素单调递增。
我们不妨枚举目标序列中间的一段元素单调递增的区间,由性质一可以得到区间左边这些元素发生关键操作顺序和右边发生关键操作顺序,因此我们可以采用 dp 处理。
定义 dpi,l,r 表示进行 i 个操作后,在目标序列所选定的区间左边的元素中还有 l 个没有发生关键操作,右边还有 r 个没有发生关键操作。
则有转移方程:
dpi+1,l,r+=dpi,l,r×2×(l+r)dpi+1,l−1,r+=dpi,l,rdpi+1,l,r−1+=dpi,l,r
答案是 dpm,0,0。
于是我们得到了 O(n3×m) 的优秀做法。
我们发现我们每次 dp 的转移类似,我们考虑一次性 dp 预处理出所以可能用到的值。
因此类似的,我们可以定义 dpi,l,r 在目标序列所选定的区间左边的元素中还有 l 个没有发生关键操作,右边还有 r 个没有发生关键操作的一种序列变成 , 在 i 次内变成目标序列的方案数。
这次我们考虑倒序还原。
我们考虑当前的情况是如何得到的,考虑正序时这一步不是任何一个元素的关键操作,则得到这样一个情况我们上一步有 (l+r)×2 个选择。
若正序时这一步是关键操作,则正序时这一步到当前只有唯一一种方式。
则有方程:
dpi+1,l,r=dpi,l,r×2×(l+r)dpi,l+1,r=dpi,l,rdpi,l,r+1=dpi,l,r
对于这个方程我们可以理解为上一个序列是已经确定的,当前这一序列是不确定的,但无论当前长什么样,推到目标序列步数都是一样的,所以不影响。
于是我们得到了 O(n2m+n2) 的优秀算法。
考虑继续优化,我们发现第二个转移方程和第三个相似,我们不妨将后两维其压缩得到 fi,l+r 。转移类似,我们考虑但是实际上, f 计算值是只考虑了发生关键操作与否的顺序,而没有考虑左右关键操作的顺序,因此需要额外计算,于是有 dpi,l,r=fi,l+r×C(l+r,l) 。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll MAXN = 3e3+10 ;
const ll mod = 998244353 ;
ll n , m ;
ll a[MAXN],f[MAXN][MAXN],C[MAXN][MAXN];
int main()
{
cin >> n >> m ;
for(ll i = 1 ; i <= n ; i ++ ) cin >> a[i] ;
f[m][0] = 1 ;
for(ll i = m - 1 ; ~i ; i -- )
{
for(ll j = 0 ; j <= n ; j ++ )
{
f[i][j] = 2 * f[i+1][j] * j % mod ;
if(j) f[i][j] = (f[i][j] + f[i+1][j-1]) % mod ;
}
}
for(ll i = 0 ; i <= n ; i ++ )
{
C[i][0] = 1 ;
for(ll j = 1 ; j <= i ; j ++ )
{
C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod ;
}
}
ll ans = 0 ;
for(ll i = 0 ; i <= n ; i ++ )
{
ans = (ans + C[n][i] * f[0][n]) % mod ;
}
for(ll i = 1 ; i <= n ; i ++ )
{
ans = (ans + C[n-1][i-1] * f[0][n-1]) % mod ;
for(ll j = i + 1 ; j <= n ; j ++ )
{
if(a[j] < a[j-1]) break ;
ans = (ans + C[n-(j-i+1)][i-1] * f[0][n-(j-i+1)]) % mod ;
}
}
cout << ans ;
return 0 ;
}
C。wine Thief
Solution:
我们先考虑n个数里选k个数的方案数f(n,k) = C(n-k+1,k) ;
然后我们考虑环上的在链上是\(a_1,a_n\)可以选但环上不行所以把\(a_1,a_n\)刨出去=F(n,k) = f(n - 3, k - 1);
然后我们算一个G(n,k,i)表示n个数选k个不连续且必须选I的方案数
if(i <= 0) return 0 ;
if(i == 1) return F(n,k) + f(n-4,k-2) ;
if(i > 1) rrturn F(n,k) + G(n-4,k-2,i-2) ;
if(i > n / 2) G(n,k,n-i+1)
740422015
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 7;
ll farc[maxn], inv[maxn];
const int mod = 998244353;
ll qpow(ll a, ll b = mod - 2)
{
ll ans = 1;
for (; b; b >>= 1, a = a * a % mod)
if (b & 1)
ans = ans * a % mod;
return ans;
}
void prework(int n = 3e5)
{
farc[0] = 1;
for (int i = 1; i <= n; i++)
farc[i] = farc[i - 1] * i % mod;
inv[n] = qpow(farc[n]);
inv[0] = 1;
for (int i = n - 1; i >= 1; i--)
inv[i] = inv[i + 1] * (i + 1) % mod;
}
ll C(int n, int m)
{
if (m > n || m < 0)
return 0;
return farc[n] * inv[m] % mod * inv[n - m] % mod;
}
ll f(ll n, ll k)
{
return C(n - k + 1, k);
}
ll F(ll n, ll k)
{
if (n < 3)
return k == 1;
else
return f(n - 3, k - 1);
}
ll sf[maxn];
ll G(ll n, ll k, ll i)
{
if (i > ceil((double)n / 2.0))
return G(n, k, n - i + 1);
if (i <= 0)
return 0;
if (i == 1)
return (F(n, k) + f(n - 4, k - 2)) % mod;
return (sf[i >> 1] + G(n - ((i >> 1) << 2), k - ((i >> 1) << 1), i & 1)) % mod;
}
int n, k, d;
ll a[maxn];
int main()
{
prework();
cin >> n >> k >> d;
ll ans = 0;
for (int i = 1; i <= n; i++)
{
sf[i] = (sf[i - 1] + F(n - ((i - 1) << 2), k - ((i - 1) << 1))) % mod;
}
for (int i = 1; i <= n; i++)
{
cin >> a[i];
ans += G(n, k, i) * a[i] % mod;
ans %= mod;【
}
cout << ans;
return 0;
}
Noip模拟赛34:
有一张 n 个点, m 条边的无问图,你想选出一个非空点集,使得仅保留这个点集中的点和两个端点都在这个点集里的边后得到的图是连通的。你想知道有多少种可能的选点集的方案。 由于出题人确实是毒瘤,所以本题不对 22 取模,改为对 998244353998244353 取模。
我们暴力的想法是:维护一个并查集,每次枚举然后删边,然后判断连通性 ;
正解的思路:状压dp维护当前点与前12个点的联通状态
#include<bits/stdc++.h>
using namespace std;
int T ;
int n, x;
int a[10];
namespace fast_IO
{
#define FAST_IO
#define IOSIZE 100000
typedef long long ll;
typedef double db;
typedef long double ldb;
typedef __int128_t i128;
char ibuf[IOSIZE], obuf[IOSIZE];
char *p1 = ibuf, *p2 = ibuf, *p3 = obuf;
#ifdef ONLINE_JUDGE
#define getchar() ((p1==p2)and(p2=(p1=ibuf)+fread(ibuf,1,IOSIZE,stdin),p1==p2)?(EOF):(*p1++))
#define putchar(x) ((p3==obuf+IOSIZE)&&(fwrite(obuf,p3-obuf,1,stdout),p3=obuf),*p3++=x)
#endif//fread in OJ, stdio in local
#define isdigit(ch) (ch>47&&ch<58)
#define isspace(ch) (ch<33&&ch!=EOF)
struct fast_IO_t{
~fast_IO_t(){
fwrite(obuf, p3-obuf, 1, stdout);
}
bool flag = false;
operator bool() {
return flag;
}
}io;
template<typename T> inline T read() {
T s = 0; int w = 1; char ch;
while(ch=getchar(), !isdigit(ch)&&(ch!=EOF))
if(ch == '-') w = -1;
if(ch == EOF) return 0;
while(isdigit(ch))
s = s*10+ch-48, ch=getchar();
if(ch == '.') {
ll flt = 0; int cnt = 0;
while(ch=getchar(), isdigit(ch))
if(cnt < 18) flt=flt*10+ch-48, cnt++;
s += (db)flt/pow(10,cnt);
}
return s *= w;
}
template<typename T> inline bool read(T &s) {
s = 0; int w = 1; char ch;
while(ch=getchar(), !isdigit(ch)&&(ch!=EOF))
if(ch == '-') w = -1;
if(ch == EOF) return false;
while(isdigit(ch))
s = s*10+ch-48, ch=getchar();
if(ch == '.') {
ll flt = 0; int cnt = 0;
while(ch=getchar(), isdigit(ch))
if(cnt < 18) flt=flt*10+ch-48, cnt++;
s += (db)flt/pow(10,cnt);
}
return s *= w, true;
}
inline bool read(char &s) {
while(s = getchar(), isspace(s));
return s != EOF;
}
inline bool read(char *s) {
char ch;
while(ch=getchar(), isspace(ch));
if(ch == EOF) return false;
while(!isspace(ch))
*s++ = ch, ch=getchar();
*s = '\000';
return true;
}
template<typename T> void print(T x) {
static int t[20]; int top = 0;
if(x < 0) putchar('-'), x = -x;
do { t[++top] = x%10; x /= 10; } while(x);
while(top) putchar(t[top--]+48);
}
struct null_type{}; int pcs = 8;
null_type setpcs(int cnt)
{
pcs = cnt;
return null_type();
}
inline void print(null_type x){}
inline void print(double x) {
if(x < 0) putchar('-'), x = -x;
x += 5.0 / pow(10,pcs+1);
print((ll)(x)); x -= (ll)(x); putchar('.');
for(int i = 1; i <= pcs; i++)
x *= 10, putchar((int)x+'0'), x -= (int)x;
}
inline void print(float x) {
if(x < 0) putchar('-'), x = -x;
x += 5.0 / pow(10,pcs+1);
print((ll)(x)); x -= (ll)(x); putchar('.');
for(int i = 1; i <= pcs; i++)
x *= 10, putchar((int)x+'0'), x -= (int)x;
}
inline void print(long double x) {
if(x < 0) putchar('-'), x = -x;
x += 5.0 / pow(10,pcs+1);
print((i128)(x)); x -= (i128)(x); putchar('.');
for(int i = 1; i <= pcs; i++)
x *= 10, putchar((int)x+'0'), x -= (int)x;
}
inline void print(char x) {
putchar(x);
}
inline void print(char *x) {
for(int i = 0; x[i]; i++)
putchar(x[i]);
}
inline void print(const char *x) {
for(int i = 0; x[i]; i++)
putchar(x[i]);
}
#ifdef _GLIBCXX_STRING//string
inline bool read(std::string& s) {
s = ""; char ch;
while(ch=getchar(), isspace(ch));
if(ch == EOF) return false;
while(!isspace(ch))
s += ch, ch = getchar();
return true;
}
inline void print(std::string x) {
for(auto i = x.begin(); i != x.end(); i++)
putchar(*i);
}
inline bool getline(fast_IO_t &io, string s)
{
s = ""; char ch = getchar();
if(ch == EOF) return false;
while(ch != '\n' and ch != EOF)
s += ch, ch = getchar();
return true;
}
#endif
template<typename T, typename... T1>
inline int read(T& a, T1&... other) {
return read(a)+read(other...);
}
template<typename T, typename... T1>
inline void print(T a, T1... other) {
print(a); print(other...);
}
template<typename T>
fast_IO_t& operator >> (fast_IO_t &io, T &b){
return io.flag=read(b), io;
}
template<typename T>
fast_IO_t& operator << (fast_IO_t &io, T b) {
return print(b), io;
}
#define cout io
#define cin io
#define endl '\n'
}
using namespace fast_IO;
int main()
{
cin >> T ;
while (T --> 0)
{
cin >> n >> x ;
int flag = 0, nmd = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i] ;
if (i > 1 && a[i] != a[i-1])
flag = 1;
}
if (n == 1) flag = 0;
if (flag == 0)
{
if (x * a[1] < 0)
{
if (abs(x) % abs(a[1]) == 0)
nmd = 1;
}
if(nmd == 0) cout << "Yes" ;
else cout << "No" ;
}
else cout << "Yes" ;
cout << endl ;
}
return 0;
}
~~~[[]]
$$
有一个数组 a_0=(1,2\dots,n,我们对它进行若干次操作,对于第 i次操作,删除数组 a_{i-1}中下标为 3k+1的所有数,将新数组作为 a_{i}
给你 n,kn,k,求出 a_k中所有数的和,对 998244353998244353 取模。
$$
看了好久题解的说
设 fi(x) 为经历了 i 轮之后的 x 的为位置对于的初始下标。
那么有 f(x)=⌊3x+12⌋
fi(x)=f(f(f()....)
考虑对这个柿子进行性质的观察。
我们最后只会剩余 frac2n3 个数,如果暴力则是 O(k(23)kn)
当 k 大时这个复杂度可以接受。
但是 k 小时,我们需要另辟蹊径。
我们发现 fk(x+i2k)=fk(x)+i3k
考虑设l+r=k
考虑折半跑路。
$f^k(x + i 2^r)= fl(fr(x) + i * 3^r) $
考虑g(a,j)=∑2j−1i=0fl(a+i3x)
我们考虑直接递推其,最后可以通过倍增和递归来处理出。
$$
~~~cpp
#include <cstdio>
#include <unordered_map>
#pragma GCC optimize(2,3,"Ofast")
using namespace std;
typedef long long ll;
const int P=998244353;
ll n,res;int k;
ll f(ll x,int i){
for(;i;--i) x=(3*x+1)/2;
return x;
}
ll o[103],w[103],msk;
int L,R;
unordered_map<ll,int> mp[103];
int g(ll x,int i){
if(x-1>msk) return (g(((x-1)&msk)+1,i)+(((x-1)>>L)%P)*(w[L]%P)%P*((1ll<<i)%P)%P)%P; //对内层的 a 进行 2^l 的平移
if(!i) return mp[i][x]=f(x,L)%P;
if(mp[i].find(x)!=mp[i].end()) return mp[i][x];
return mp[i][x]=(g(x,i-1)+g(x+w[R]*(1ll<<(i-1)),i-1))%P;
}
int main(){
scanf("%lld%d",&n,&k);
o[0]=n;
for(int i=1;i<=k;++i) o[i]=o[i-1]*2/3;
if(k>40){//是的……需要数据分治……毒瘤
int res=0;
for(int i=1;i<=o[k];++i)
res=(res+f(i,k))%P;
printf("%d\n",res);
return 0;
}
L=k/2;R=k-L;w[0]=1;msk=(1ll<<L)-1;
for(int i=1;i<=30;++i) w[i]=w[i-1]*3;
for(int i=1;i<=o[k]&&i<=(1ll<<R);++i){
ll c=f(i,R),upb=((o[k]-i)>>R)+1;
for(int j=50;~j;--j)
if(upb>>j&1){
res=(res+g(c,j))%P;
c+=w[R]<<j; //对外层的 a 进行 2^r 的平移,拼接成一段前缀
}
}
printf("%lld\n",res);
return 0;
}
C: Priority Queue
给你一个长度 A+BA+B 的整数序列 (x_1, x_2,…, x_{A+B}),其中包含 A 个 1 和 B 个2。
Snuke有一个集合 ss ,最初是空的。它要进行 A+BA+B 次操作。第 ii 个操作如下所示。
如果 x_i =1选择一个整数 v(1≤v≤A),并将其加到 s 中。在这里,v 必须不是之前的操作中选择过的整数。
如果 x_i =2: 从 s 中删除值最大的元素。输入确保在这个操作之前s不是空的。
输出 s 的最终状态有多少种可能,答案对 998244353取模。
Solution:
如果把所有可能的最终集合排序并且找到字典序(我也不知道怎么表述了……)最大的。显然就是在第 i 次插入时插入 i 可以使得字典序最大。
那么,如果一种集合的字典序大于这个最大的字典序,那肯定无法构造。反之则必定可以构造,下面是一种方案
把所有出现在最终集合中的数从小到大插入,如果碰到操作2就插一个不在最终集合内的数垫背。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+50;
const int mod = 998244353 ;
int f[3][maxn] ;
int n ,top , sta[maxn] , cnt , w = 1 ;
int ans , m ;
int main()
{
cin >> n >> m ;
for(int i = 1 ; i <= n + m ; i ++ )
{
int x ; cin >> x ;
if(x == 1) sta[++top] = ++cnt ;
else --top ;
}
f[0][0] = 1 ;
for(int i = 1 ; i <= top ; i ++ , w ^= 1)
{
f[w][0] = 0 ;
for(int j = 1 ; j <= sta[i] ; j ++ )
{
f[w][j] = (f[w][j-1] + f[w^1][j-1]) % mod ;
}
}
w ^= 1 ;
for(int i = 1 ; i <= sta[top] ; i ++ )
{
ans = ( ans + f[w][i]) % mod ;
}
cout << ans ;
}
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+50;
const int mod = 998244353 ;
int f[3][maxn] ;[[]]
int n ,top , sta[maxn] , cnt , w = 1 ;
int ans , m ;
int main()
{
cin >> n >> m ;
for(int i = 1 ; i <= n + m ; i ++ )
{
int x ; cin >> x ;
if(x == 1) sta[++top] = ++cnt ;
else --top ;
}
f[0][0] = 1 ;
for(int i = 1 ; i <= top ; i ++ , w ^= 1)
{
f[w][0] = 0 ;
for(int j = 1 ; j <= sta[i] ; j ++ )
{
f[w][j] = (f[w][j-1] + f[w^1][j-1]) % mod ;
}
}
w ^= 1 ;
for(int i = 1 ; i <= sta[top] ; i ++ )
{
ans = ( ans + f[w][i]) % mod ;
}
cout << ans ;
}
Day 9.26 :
题目大意:
给定一个长度为n的序列\(S\),然后给定一个mod,有q次操作;
-
1 l r x , 对$ \forall i\in[l,r],a_i\gets a_i\times x$
-
2 p x
,令 \(a_p\gets \dfrac{a_p}{x},保证 x\mid a_p\) -
3 l r
,查询$ \displaystyle\sum_{i=l}^ra_i$
Solution:
对于这个mod他不一定是奇素数,所以我们先把mod唯一分解,对于一个数可以拆成\(p1 * p2 * .. * val\),\(val\)不是\(prime\),我们开一颗线段树维护\(prime\)桶和val,对于乘操作,我们把x拆成质因子 * val的形式对于val直接乘,质因子就当成lz加上去,对于除法也一样,最后维护一个sum就没了
#include<bits/stdc++.h>
using namespace std ;
template <typename T>
void read(T &a)
{
T x = 0 , f = 1;
char ch = getchar() ;
while(!isdigit(ch))
{
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<1)+(x<<3)+(ch^48) ;
ch=getchar();
}
a = x*f;
}
template <typename T,typename... Args> void read(T &t,Args &... args) {read(t); read(args...); }
#define ll long long
const ll MAXN = 4e6 ;
ll n,q,mod;
ll cnt;
ll a[MAXN] ;
map<ll,ll> mp,Prime ;
void exgcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
{
x=1,y=0;
return ;
}
exgcd(b,a%b,y,x) ;
y -= 1ll * a / b * x ;
}
inline ll inv(ll x)
{
ll a , b ;
exgcd(x,mod,a,b);
return (a%mod+mod)%mod;
}
inline ll ksm(ll a,ll b)
{
ll res=1;
for(;b;b>>=1,a=a*a%mod)
{
if(b&1) res=res*a%mod;
}
return res;
}
namespace Segment_Tree
{
ll sum[MAXN];
ll tr[MAXN];
ll PrimeTon[MAXN][18]; // 因数桶
ll lz[MAXN] ;
inline void push_up(ll i)
{
sum[i] = (sum[i<<1] + sum[i<<1|1]) % mod ;
}
inline void push_down(ll i)
{
sum[i<<1] = 1ll * sum[i<<1] * lz[i] % mod ;
lz[i<<1] = 1ll * lz[i<<1] * lz[i] % mod ;
tr[i<<1] = 1ll * tr[i<<1] * tr[i] % mod ;
sum[i<<1|1] = 1ll * sum[i<<1|1] * lz[i] % mod ;
lz[i<<1|1] = 1ll * lz[i<<1|1] * lz[i] % mod ;
tr[i<<1|1] = 1ll * tr[i<<1|1] * tr[i] % mod ;
tr[i] = 1 , lz[i] =1 ;
for(ll j = 1 ; j <= cnt ; j ++ )
{
PrimeTon[i<<1][j] += PrimeTon[i][j] ;
PrimeTon[i<<1|1][j] += PrimeTon[i][j] ;
PrimeTon[i][j] = 0;
}
}
inline void build(ll i , ll l , ll r)
{
lz[i] = 1 ; tr[i] = 1 ;
if(l == r)
{
sum[i] = a[l] % mod ;
ll x = a[l] ;
for(ll j = 1 ; j <= cnt ; j ++ )
{
while(!(x%Prime[j])) PrimeTon[i][j] ++ , x /= Prime[j] ;
}
tr[i] = x % mod ;
return ;
}
ll mid = (l + r) >> 1 ;
build(i << 1 , l , mid) ;
build(i << 1 | 1 , mid + 1 ,r) ;
push_up(i) ;
}
inline void update(ll i,ll l,ll r,ll p,ll x)
{
if(l == r)
{
for(ll j = 1 ; j <= cnt ; j ++ )
{
while(!(x % Prime[j])) PrimeTon[i][j] -- , x /= Prime[j] ;
}
tr[i] = 1ll * tr[i] * inv(x) % mod ;
sum[i] = tr[i] ;
for(ll j = 1 ; j <= cnt ; j ++ )
{
sum[i] = 1ll * sum[i] * ksm(Prime[j] , PrimeTon[i][j]) % mod ;
}
return ;
}
push_down(i) ;
ll mid = (l + r) >> 1 ;
if(p <= mid) update(i<<1,l,mid,p,x) ;
if(p > mid) update(i<<1|1,mid+1,r,p,x) ;
push_up(i) ;
}
inline void update(ll i , ll l , ll r , ll L ,ll R , ll x)
{
if(L <= l && r <= R)
{
sum[i] = 1ll * sum[i] * x % mod ;
lz[i] = 1ll * lz[i] * x % mod ;
for(ll j = 1 ; j <= cnt ; j ++ )
{
while(!(x % Prime[j])) PrimeTon[i][j] ++ , x /= Prime[j] ;
}
tr[i] = 1ll * tr[i] * x % mod ;
return ;
}
ll mid = (l + r) >> 1 ;
push_down(i) ;
if(L <= mid) update(i<<1,l,mid,L,R,x) ;
if(R > mid) update(i<<1|1,mid+1,r,L,R,x) ;
push_up(i) ;
}
inline ll query(ll i,ll l,ll r,ll L,ll R)
{
if(L <= l && r <= R)
{
return sum[i] ;
}
push_down(i) ;
ll mid = (l + r) >> 1 ;
ll ans = 0 ;
if(L <= mid) ans = (ans + query(i<<1,l,mid,L,R)) % mod ;
if(R > mid) ans = (ans + query(i<<1|1,mid+1,r,L,R)) % mod ;
return ans ;
}
};
using namespace Segment_Tree ;
signed main()
{
read(n,mod) ;
ll _mod = mod ;
for(ll i = 2 ; i * i <= _mod ; i ++ )
{
if(_mod % i) continue ;
mp[i] = ++ cnt ;
Prime[cnt] = i ;
while(!(_mod % i)) _mod /= i ;
}
if(_mod != 1) mp[_mod] = ++ cnt , Prime[cnt] = _mod ;
for(ll i = 1 ; i <= n ; i ++ ) read(a[i]) ;
build(1 , 1 , n) ;
read(q) ;
while(q-->0)
{
ll opt;
read(opt);
if (opt == 1)
{
ll l,r,x;
read(l,r,x);
update(1,1,n,l,r,x);
}
if (opt == 2)
{
ll p,x;
read(p,x);
update(1,1,n,p,x);
}
if (opt == 3)
{
ll l,r;
read(l,r);
ll ans = query(1,1,n,l,r);
cout << ans << endl ;
}
}
}
Squirrel Migration
给你一个n 个结点的树,询问有多少个 $1\dots n $ 的排列 p 满足 \(\sum_{i=1}^n dis(i,p_i)\)∑i=1 最大。其中 dis(x,y) 表示树上 \(x\to y\) 的路径经过的边数。
考虑一条链的时候我们肯定是从中间断开左->右,右->左
那对于一棵树而言\(\sum_{i=1}^{n}dis(i,p_i)\)可以写成\(\sum_{i=1}^{n}dis(1,i)+dis(1,p_i)-2*dis(1,lca(i,p_i))\)然后我们就使\(lca(i,p_i)\)深度越小越好,我们还能知道的是对于两棵子树肯定是互相指最优就跟链一样,所有我们考虑把重心搞出来当根
我们考虑熔池原理,首先看有哪些不合法的,一种是两个点都在同一颗子树内,还有一种是两个点互指
推出来的狮子就是
最后跑一个树上背包统计节点答案时间复杂度是\(n^2\)de
#include <bits/stdc++.h>
using namespace std;
#define MAXN 5005
#define mod 1000000007
#define pb push_back
int n, tot, MaxZo, ans, fac[MAXN], inv[MAXN], siz[MAXN], dp[MAXN];
vector<int> e[MAXN];
void dfs(int u, int f)
{
int mx = 0;
siz[u] = 1;
for (int i = 0, v; i < e[u].size(); ++i)
{
v = e[u][i];
if (v != f)
dfs(v, u), siz[u] += siz[v], mx = max(mx, siz[v]);
}
mx = max(mx, n - siz[u]);
if (mx <= n / 2)
++tot, MaxZo = u;
}
int ksm(int x, int y)
{
int res = 1;
for (; y; y /= 2, x = 1ll * x * x % mod)
if (y & 1)
res = 1ll * res * x % mod;
return res;
}
int C(int x, int y) { return 1ll * fac[x] * inv[y] % mod * inv[x - y] % mod; }
int main()
{
scanf("%d", &n);
fac[0] = inv[0] = dp[0] = 1;
for (int i = 1; i <= n; ++i)
fac[i] = 1ll * fac[i - 1] * i % mod;
inv[n] = ksm(fac[n], mod - 2);
for (int i = n - 1; i; --i)
inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
for (int i = 1, u, v; i < n; ++i)
scanf("%d %d", &u, &v), e[u].pb(v), e[v].pb(u);
dfs(1, 0);
dfs(MaxZo, 0);
for (int i = 0, x, w; i < e[MaxZo].size(); ++i)
{
x = siz[e[MaxZo][i]];
for (int j = n; j >= 0; --j)
for (int k = 1; k <= min(j, x); ++k)
{
w = 1ll * C(x, k) * fac[x] % mod * inv[x - k] % mod;
dp[j] = (dp[j] + 1ll * dp[j - k] * w) % mod;
}
}
for (int i = 0; i <= n; ++i)
ans = (ans + (i & 1 ? -1ll : 1ll) * dp[i] * fac[n - i]) % mod;
printf("%d\n", (ans + mod) % mod);
return 0;
}
Dayn.nn
依旧是熟悉的GZEZ
A.:
绐定一棵 nn 个结点的树,其结点编号为 1,2, \ldots, 每条边长度为1现在要举行Q次会议。第 i议从编号为 l_i到 r_i的结点各邀请名代表,到达一处共同的会议地点。会议的总距离,就是每名与会代表到会议离和。会议的最佳地点,就是树上能最小化会议的总距离的结点。请你为每次会议选择最佳她点因为可能有多解,所以你只需输出最小总距离。
How to solve it ?
不会写,但可以口胡一胡
有个好玩的东西叫三度化,我们先考率一个点连接A,B,C三部分若要使得总距离最小就Kengding要往大的方向走
三度化的具体作用是:将带权树,在不改变任意两点间距离、任意两点间祖先后代关系的情况下,转化成每个点度数至多为3的点数至多翻倍的树.,我们单次可以 O(\log^2n)找到一个区间的关键点的重心再离线下来用点分治求出在原树上对应的重心到所有点的距离,可以做到O((n+q)\log n)。
B. 咕了,期望dp还不hi
C.:
A Stroll Around the Matrix:
给你两个严格上升的序列,保证它们的差分序列也严格递增,其中一个长度不超过 100。
每次操作会选择一个序列,给后面的 k 个加一个首项和公差相同的等差序列,然后构造一个 n*m 的棋盘,(i,j) 位置的值是第一个数组 i 位置的值加上第二个数组 j 位置的值。
问你从 (1,1) 走到 (n,m) 只往右向下走的最小费用和。
Solution:
向下和向右的差分数组是越来越大的
\(a_{i+1}+b_{i+1}>a_{i}+b_{i}\) -> \(a_{i+1}-a_{i}<b_{i+1}-b_{i}\) 所以我们可以知道贪心走小的是最优那我们一个贪心的想法是先放这个值小的。再加上差分数组也严格递增,那不就是把两个差分后得到的数组归并一下!然后考虑怎么归并,发现有一个特别小,那是不是可以直接枚举小的每个数插入到大的,插到线段树上二分。然后注意插入之后不仅要有自己的贡献,还有 b 前面部分因为它能多贡献一次。
T1:小猫
斜率优化。
我们考虑每个猫的结束时间减去它的坐标,就相当于所有猫都在节点1,只是结束的时间不同了。
我们再把这个结束的时间排序一下,就可以设dp[i][j]表示前i只猫,被j个饲养员带走的最小代价了。
转移方程为:dp[i][j]=mindp[i][j],dp[p][j−1]+node[i].num∗(i−p)−(sum[i]−sum[p])
其中sum[i]表示前i只猫的等待时间前缀和,node[i].num表示该猫等效于在1节点的开始等待时刻。
然后这个朴素DP是40分的。
现在考虑斜率优化:
把式子移项一下:
dp[i][j]+node[i].num∗p=dp[p][j−1]+node[i].num∗i−sum[i]+sum[p]
这就有了b+kx=y的形式
维护下凸壳即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1e5+10;
int n, m, p ;
int head, tail, now;
int ans;
int f[110][MAXN] ;
int a[MAXN], b[MAXN], sum[MAXN], dis[MAXN];
inline void upmin(int &a, int b){
if(b < a) a = b;
}
inline int g(int k){
return sum[k] + f[now][k];
}
inline int cross(int a, int b, int c)
{
int x1 = a - b, x2 = b - c, y1 = g(a) - g(b), y2 = g(b) - g(c);
return x1 * y2 - x2 * y1;
}
inline int cal(int x, int k)
{
return g(x) - b[k] * x;
}
signed main()
{
cin >> n >> m >> p ;
for(int i = 2; i <= n; i ++) cin >> a[i] , a[i] += a[i - 1];
for(int i = 1; i <= m; i ++)
{
int x ; cin >> x ;
cin >> b[i] ;
b[i] -= a[x] ;
}
sort(b + 1, b + m + 1);
for(int i = 1; i <= m; i ++) sum[i] = sum[i - 1] + b[i] ;
for(int i = 1; i <= m; i ++)
f[1][i] = i * b[i] - sum[i];
ans = f[1][m];
for(int i = 2; i <= p; i ++)
{
head = 1, tail = 0, now = i - 1;
dis[++tail] = 0;
for(int j = 1; j <= m; j ++)
{
while(head < tail && cal(dis[head + 1], j) < cal(dis[head], j)) ++ head;
int x = dis[head];
f[i][j] = f[i - 1][x] + (j - x) * b[j] - sum[j] + sum[x];
while(head < tail && cross(j, dis[tail], dis[tail - 1]) > 0) -- tail;
dis[++ tail] = j;
}
upmin(ans, f[i][m]);
}
printf("%lld\n", ans);
return 0;
}
T3:Decinc Dividing
义一个序列 a 是好的,仅当可以通过删除 a 的一个单调递减子序列(可以为空)使得 a 单调递增。给定一个 n 阶的排列 p,你需要求出 p 有多少子段是好的。
若存在一段合法区间 [l..r],那么 ∀l⩽i⩽j⩽r,有 [i..j] 是合法区间,这样就证明了答案有单调性,即对于某个位置 i,定义 wi 是最大的满足 [i..wi] 为合法区间的正整数,那么有 wi⩾wi−1。
考虑类似整体二分的做法,对于区间 [l..r],我们先求出 wl 和 wr,若 wl=wr,则将满足 l⩽i⩽r 的 wi 都赋值为 wl,否则设 m=⌊l+r2⌋ 并递归处理 [l..m] 和 [m..r]。最后的答案即为 (∑i=1nwi)−n(n−1)2。
#include <bits/stdc++.h>
using namespace std ;
const int MAXN = 1e6 ;
typedef long long ll;
int ansl[MAXN],ansr[MAXN] ;
ll n ;
ll a[MAXN] ;
ll f[MAXN][2] ;
ll ans[MAXN] ;
inline void calc(int k)
{
f[k][0] = n + 1 ;
f[k][1] = 0 ;
for (int i = k + 1 ; i <= n ; i ++ )
{
f[i][0] = 0 , f[i][1] = n + 1 ;
if(a[i-1] < a[i]) f[i][0] = f[i-1][0] ;
if(a[i-1] > a[i]) f[i][1] = f[i-1][1] ;
if (f[i - 1][1] < a[i]) f[i][0] = max(f[i][0], a[i - 1]);
if (f[i - 1][0] > a[i]) f[i][1] = min(f[i][1], a[i - 1]);
if (f[i][0] == 0 && f[i][1] == n + 1)
{
ans[k] = i - 1;
return ;
}
}
ans[k] = n ;
}
inline void solve(int l , int r)
{
if(l + 1 >= r) return ;
if (ans[l] == ans[r])
{
for (int i = l ; i <= r; i ++ )
{
ans[i] = ans[l];
}
return;
}
int mid = (l + r) >> 1;
calc(mid);
solve(l , mid);
solve(mid , r);
}
inline void work()
{
cin >> n ; for (int i = 1 ; i <= n ; i ++ ) cin >> a[i] ;
calc(1) , calc(n);
solve(1, n);[]
}
int main()
{
work() ;
ll res = 0;
for (int i = 1; i <= n; i ++ )
{
res += ans[i];
}
cout << res - n * (n - 1) / 2 ;
return 0;
}
Problem B: Pairwise
给定一个数列A求
#include <bits/stdc++.h>
using namespace std;
#define ll long long
namespace fast_IO
{
#define FASTIO
#define IOSIZE 100000
char ibuf[IOSIZE], obuf[IOSIZE];
char *p1 = ibuf, *p2 = ibuf, *p3 = obuf;
#ifdef ONLINE_JUDGE
#define getchar() ((p1==p2)and(p2=(p1=ibuf)+fread(ibuf,1,IOSIZE,stdin),p1==p2)?(EOF):(*p1++))
#define putchar(x) ((p3==obuf+IOSIZE)&&(fwrite(obuf,p3-obuf,1,stdout),p3=obuf),*p3++=x)
#endif//fread in OJ, stdio in local
#define isdigit(ch) (ch>47&&ch<58)
#define isspace(ch) (ch<33)
template<typename T> inline T read()
{
T s = 0; int w = 1; char ch;
while(ch=getchar(),!isdigit(ch)and(ch!=EOF)) if(ch=='-') w=-1;
if(ch == EOF) return false;
while(isdigit(ch)) s=s*10+ch-48,ch=getchar();
return s*w;
}
template<typename T> inline bool read(T &s)
{
s = 0; int w = 1; char ch;
while(ch=getchar(),!isdigit(ch)and(ch!=EOF)) if(ch=='-') w=-1;
if(ch == EOF) return false;
while(isdigit(ch)) s=s*10+ch-48,ch=getchar();
} return s*=w, true;
inline bool read(char &s)
{
while(s = getchar(), isspace(s));
return true;
}
inline bool read(char *s)
{
char ch;
while(ch=getchar(),isspace(ch));
if(ch == EOF) return false;
while(!isspace(ch)) *s++ = ch, ch = getchar();
*s = '\000'; return true;
}
template<typename T> inline void print(T x)
{
if(x < 0) putchar('-'), x = -x;
if(x > 9) print(x/10);
putchar(x%10+48);
}
inline void print(char x){ putchar(x); }
inline void print(char *x){ while(*x) putchar(*x++); }
inline void print(const char *x)
{ for(int i = 0; x[i]; i++) putchar(x[i]); }
#ifdef _GLIBCXX_STRING
inline bool read(std::string& s)
{
s = ""; char ch;
while(ch=getchar(),isspace(ch));
if(ch == EOF) return false;
while(!isspace(ch)) s += ch, ch = getchar();
return true;
}
inline void print(std::string x)
{
for(int i = 0, n = x.size(); i < n; i++)
putchar(x[i]);
}
#endif//string
template<typename T, typename... T1> inline int read(T& a, T1&... other){ return read(a)+read(other...); }
template<typename T, typename... T1> inline void print(T a, T1... other){ print(a); print(other...); }
struct Fast_IO{
~Fast_IO(){ fwrite(obuf, p3-obuf, 1, stdout); }
}io;
template<typename T> Fast_IO& operator >> (Fast_IO &io, T &b){ return read(b), io; }
template<typename T> Fast_IO& operator << (Fast_IO &io, T b){ return print(b), io; }
#define cout io
#define cin io
#define endl '\n'
}
using namespace fast_IO;
const ll MAXN = 3e5 + 5;
ll n, sum, res;
struct BIT
{
ll t[MAXN];
ll lowbit(ll x)
{
return (x & -x) ;
}
void add(ll x, ll v)
{
while (x < MAXN)
{
t[x] += v ;
x += lowbit(x);
}
}
ll query(ll x)
{
ll res = 0;
while (x)
{
res += t[x] ;
x -= lowbit(x);
}
return res;
}
} A, B;
int main()
{
cin >> n;
for (ll i = 1, a; i <= n; i++)
{
cin >> a ;
res += sum + a * (i - 1) - A.query(a);
for (ll j = a; j < MAXN; j += a)
{
int L = min(MAXN - 1, j + a - 1);
res -= j * (B.query(L) - B.query(j - 1));
A.add(j, j) ;
A.add(L + 1, -j);
}
B.add(a, 1), sum += a ;
cout << res << " ";
}
return 0;
}
Problem A: sequence
考虑搜索,我们meet-in-the-middle从两边向中间搜相遇就停下
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll s;
ll a[10][30];
vector<ll> anss;
int n;
int TO;
ll ans = 0;
void dfs1(int x,ll now)
{
if(now > s) return ;
if(x==n+1)
{
anss.push_back(now);
return ;
}
for(int i = 1 ; i <= 30 ; i ++ )
{
if(a[x][i] + now > s) break;
dfs1(x + 1 , now + a[x][i]);
}
}
void dfs2(int x,ll now)
{
if(now>s) return ;
if(x==TO)
{
ans += upper_bound(anss.begin() , anss.end(),s-now) - anss.begin();
return ;
}
for(int i = 1 ; i <= 30 ; i ++ )
{
if(a[x][i] + now > s) break;
dfs2(x + 1 , now + a[x][i]);
}
}
int main()
{
cin >> n >> s;
for(int i = 1 ; i <= n ; i ++ ) cin >> a[i][1];
for(int i = 1 ; i <= n ; i ++ )
for(int j=2;j<=30 && a[i][j-1] <= s;j++)
a[i][j] = a[i][j-1] * a[i][1];
if(n==1){
int i;
for(i=1;i<=30;i++){
if(a[1][i] > s) break;
}
cout << i-1;
exit(0);
}
int mid = n / 2 ;
dfs1(n-mid+1,0);
TO=n-mid+1;
sort(anss.begin(),anss.end());
dfs2(1,0);
cout << ans;
return 0;
}
T2:name
直接枚举父亲
若u<v这时候我们只用考虑v所以说我们枚举v的父亲然后递推一下对于每种情况的父亲统计他们的贡献和最后再除上父亲总数
Problem A: 小D的序列
暴力的做法就是枚举这个a然后\(O(n)\)扫一遍我真傻真的,我没用map
其实正解也挺吊的 就是说你要统计一个序列的权值和和乘积,然后因为\(mod p\)的情况下值不变所以我们只需要用等比数列求和,求乘积公式判断是否满足条件即可
Problem B: 小S排座位
答案的配对方式肯定是一段前缀和一段后缀按顺序进行匹配。
n=read();
k=read();
mid=n>>1;
for(int i=1;i<=mid;++i)
a[i]=read();
int l=1;
for(;l<=mid&&mid+1<=n;n--)
{
int t=read();
if(ab(t-a[l])>=k)
l++;
}
print(l-1);
return 0;
Problem C: 小K的外挂
考虑记录当前点能向前到达的最大值和次大值然后倍增跳我们知道当前点的最大值相当去前面点的最大值
Problem A: 分组:
题目描述
现在有 nn 个字符串,你需要从中选出一些字符串,使得选出的字符串能被分组,满足每组大小为 22 ,且可以从每组选出该组的两个字符串的一个非空公共后缀,使得每组选出的串互不相同。
我们暴力的思路的就是枚举匹配对数和它们的公共后缀字串然后判断,正解的话就是把他们倒过来建一颗Trie树暴力dfs求解
Problem B: Non-coprime DAG
我们考虑分奇数和偶数的情况,设\(f(x)\)为\(x\)的最小质因子,x若能到达y要满足:
我们考虑怎么转移,这里有一个特殊的结论设
\(\\
G(x) =
\begin{cases} [n,n] \quad 2 \mid x
\\ [n-f(n)+1,n+f(n)-1] \quad 2 \nmid x
\end{cases}
\)
两个节点x,y不可达当且仅当G(x)与G(y)相交
统计一个前缀和取max即可
Problem C: 美好的每一天~不连续的存在
YNOI不hui
四 十 六
Problem A: brime
Solution:
类质数:
我们在 $l , min(Lim,k)$用欧拉筛筛到的数在$(1,2 * min(Lim,k)$ 间标记最后访问到问被标记的记作答案即可
Problem B: sequence
Solution:\(
先考虑怎么算不同子序列个数。设\)dp[i]$ 表示当前以 i 结尾的子序列个数,如果整个序列的下一个元素 是 x ,那么令\(d p[x]=1+\sum_{i=1}^{k} dp[i]\) ,其它的dp值保持不变。
注意到无论接下来的元素是什么,新的\(dp[x]\)都是一样的。由于我们要最大\(\sum_{i=1}^{k}\),很容易想到贪 心地填当前的dp值最小的那个元素,其实也就是 最后出现位置最靠前的那个元素。
直接填是\(O(m)\)的,不难发现我们填的一定是一个k的排列重复若干次。这样每k个元素的 转移都是 相同的,由于转移是一个线性递推式,我们先把转移k次总的转移系数算出来,然后矩阵乘法就行了。
$O\left(n+k^{3} \log m\right) $
Problem C: iiidx
原题自己找去
Solution
四 十 五
T1:Problem A
你一个数列 \(A_{1..n}(A_i>0)\),对于 1..n中的每个 K,你要把它分成 K 个非空的连续段。对每段分别求和,得到 KK 个数,请你最大化它们的最大公约数,并输出这个值。
Solution:
K 段求和的 gcd 等于 K 段右端点前缀和的 gcd
也就是求最大的数 x 使得 x|sumN,且有 K−1 个 p 满足 x|sump
可以 O(N+V)
Problem A:sequence
考虑静态版本的做法
只考虑一种单峰,另一种是一样的
交换次数是对应原数组下标的逆序对个数
从小到大排序后从两边向中间加数,可以放前面或后面
贡献是对中间还没有加入的数的逆序对个数,也就是没放的数中 下标比它大的 比 下标比它小的 更多就放前面,否则放后面
插入一个数,考虑它的影响
它的下标一定是最大的,肯定放后面,本身不产生贡献
但在它前面加入且放后面的数贡献+1,且一些放后面的数会改放前面
对于每个数找到它改放在前面的时刻
此时没放的数中 下标比它大的 超过 下标比它小的,下标比它小的数不会变化,也就是要找下标比它大的数中第k个值比它大的数的下标
主席树上二分即可
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 10;
const int INF = 1e18;
int n;
int a[N], h[N];
vector <int> vec[N];
int bit[N];
int lowbit(int x){return x & (-x);}
void add(int x, int k)
{
for(int i = x; i <= n; i += lowbit(i))
bit[i] += k;
}
int sum(int x)
{
int s = 0;
for(int i = x; i >= 1; i -= lowbit(i))
s += bit[i];
return s;
}
int f[N], pos[N];
int ans[N];
void solve()
{
for(int i = 1; i <= n + 1; i++)
vec[i].clear(), bit[i] = 0;
for(int i = 1; i <= n; i++)
{
add(a[i], 1);
f[i] = sum(a[i] - 1);
pos[a[i]] = i;
}
for(int i = 1; i <= n + 1; i++)
bit[i] = 0;
for(int i = 1; i <= n; i++)
{
int l = pos[i], r = n, x;
while(l <= r)
{
int mid = (l + r) >> 1;
if(sum(mid) - f[pos[i]] <= f[pos[i]])
l = mid + 1, x = mid;
else r = mid - 1;
}
vec[x].push_back(i);
add(pos[i], 1);
}
for(int i = 1; i <= n + 1; i++)
bit[i] = 0;
int now = 0;
for(int i = 1; i <= n; i++)
{
now += sum(n) - sum(a[i]);
for(int v : vec[i]) add(v, -1);
add(a[i], 1);
ans[i] = min(ans[i], now);
}
}
signed main()
{
scanf("%lld", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]), h[i] = a[i];
sort(h + 1, h + n + 1);
for(int i = 1; i <= n; i++)
a[i] = lower_bound(h + 1, h + n + 1, a[i]) - h;
for(int i = 1; i <= n; i++)
ans[i] = INF;
solve();
for(int i = 1; i <= n; i++)
a[i] = n + 1 - a[i];
solve();
for(int i = 1; i <= n; i++)
printf("%lld\n", ans[i]);
return 0;
}
假若说我当前已经进行完了i的训练任务,在第i-1的时候我们需要从第i个任务中取出圆盘那么我只要直到一个栈里上层的值满足下一层的代价最小即可,我们可以直接转移