CSP考前总结
10.2
考试:
1.数位DP 或者找规律
2.SB题,扫一遍找最大最小即可
3.莫比乌斯反演
出题人相出个数论和数据结构的综合题,但是找不到NOIP级别的,没办法只能忍痛割爱出个莫比乌斯,话说回来,莫比乌斯要是会了,其他的应该也就会了……(吧
看这个操作 1 n d v
相当于 \(a[x] += v[\gcd(x,n)=d]\)
\(v[\gcd(x,n) = d] = v [\gcd(\frac{x}{d},\frac{n}{d})=1] = v\sum\limits_{k|\gcd(\frac{x}{d},\frac{n}{d})} \mu(k) = v\sum\limits_{k|\frac{x}{d},k|\frac{n}{d}} \mu(k)\)
\(=\sum\limits_{k|\frac{n}{d},kd|x} v\mu(k)\)
如果按照操作来说我们现在其实就是枚举了所有的 \(\frac{n}{d}\) 的约数 \(k\), 然后枚举所有 \(kd\) 的倍数,对应位置加上 \(v\mu(k)\) 就行了。 查询 \(O(1)\) 查询
但是这样修改的复杂度太大,查询的复杂度太低,不妨让修改的时候枚举约数,查询的时候也枚举约数。也即我们修改的时候,新开一个数组 \(f\),枚举约数之后只让 \(kd\) 加上 \(v\mu(k)\) 查询的时候查询\(\sum\limits_{i=1}^n\sum\limits_{d|i} f(d)=\sum\limits_{d=1}^n f(d)\lfloor \frac{n}{d}\rfloor\)
后面的东西跟根号分块就行了,前面的东西始终是个区间和,单点修改区间求和,使用树状数组就可以了
时间复杂度$O(q\sqrt{l}\log l+ l \log l) $
4.代码:
int mu[N],prime[N],vis[N],cnt,tot,p[N],l,m,val[N];
void add(int x,int v){
while(x<=l){
val[x]+=v; x+=x&(-x);
}
}
int ask(int pos){
int sum=0;
while(pos){
sum+=val[pos];
pos-=pos&(-pos);
}
return sum;
}
void yilin()
{
mu[1]=1;
for(int i=2;i<=200000;i++){
if(!vis[i]){
prime[++cnt]=i;
mu[i]=-1;
}
for(int j=1;j<=cnt;j++){
vis[i*prime[j]]=1;
if(i%prime[j]==0){
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
}
int main(){
yilin();
while(1){
memset(val,0,sizeof(val));
printf("Case #%lld:\n",++tot);
l=read(); m=read();
if(l==0 && m==0)break;
for(int i=1;i<=m;i++){
int opt,n,d,v; opt=read();
if(opt==1){
n=read(); d=read(); v=read();
if(n%d!=0)continue;
for(int k=1; k*k<=(n/d); k++)
{
if((n/d)%k==0)add(k*d,v*mu[k]);
if(k*k!=n/d)add( d*( (n/d)/k ),v*mu[(n/d)/k]);
}
}
else{
x=read();
int ans=0;
for(int l=1,r;l<=x;l=r+1){
r=min(x,x / (x / l));
ans+=(x/l) * ((ask(r)-ask(l-1));
}
printf("5d\n",ans);
}
}
}
}
5.裴蜀定理
\(ax+by=m\)有整数解时当且仅当m是\(gcd(a,b)\)的倍数。裴蜀等式有解时必然有无穷多个整数解,每组解x、y都称为裴蜀数,可用扩展欧几里得算法求得。
例如,12和42的最大公约数是6,则方程\(12x+42y=6\)有解。事实上有(-3)×12 + 1×42 = 6及4×12 + (-1)×42 =6。特别来说,方程 ax+by=1 有整数解当且仅当整数a和b互素。
6.数论分块
整除分块快速处理:\(\huge\sum_{i=1}^{n}{\lfloor \frac{n}{i} \rfloor}\)
通过打表找规律,可以发现 特定块的\(\lfloor \frac{n}{i} \rfloor\)的值是相同的,假设起始位置为l,那么结束位置\(\lfloor \frac{n}{\lfloor \frac{n}{l}\rfloor} \rfloor\)
for(int l=1,r;l<=x;l=r+1){
r=min(x,x / (x / l));
ans+=(x/l) * ((ask(r)-ask(l-1));}
10.3
BZOJ 4879
对于每个点分三种情况
- 没走过这个点,有颜色,无解
- 走过这个点,有颜色,那么最早时刻在最后一次经过这个点之后
- 走过这个点,没颜色,那么最晚时刻在最后一次经过这个点之后
原问题变为了求最后一次经过某个点的时间
倒过来做变成第一次经过某个点的时间
一次操作无非就是横着擦一下或者竖着擦一下,先只考虑横着擦一下,我们把原图从上到下,从左到右编号,那么每次操作相当于把原序列中没有数字的部分都抹成某个数字,这个题就变成了BZOJ 疯狂的馒头,直接并查集找最右面的点就 OK 了
还有竖着的怎么半?分两次做,一次只做横着的,从上到下从左到右编号,一次只做竖着的从左到右从上到下编号,两次答案取交集就可以了
时间复杂度 \(O(\text{union-find-set})\)
即使你写的是 \(n\log n\) 也能通过测试数据,但是 \(set\) 模拟的做法大概率会 T
可以用最小生成树,建边从0~n.
10.4
1.能量项链 考虑中间的十分,每种颜色值出现了两次我们可以在一种颜色出现第一次的时候 +1, 出现第二次的时候 -1,那么前缀和相等的一对点一定可以成为一对分割点 ,如果颜色出现了不止一次的话,+1,-1显然不行
所以我们可以每次加减一个不同的很大整数来避免重复。
2.动态规划,每次倒着扫就行
3.树剖加DP
考虑这样一个问题,要怎么样的点才能满足三个点两两距离相等呢?
1、存在三个点有共同的 \(lca\)。
2、存在一个点,使得它到它两颗不同的子树种两点的距离为 \(d\) 且它存在 \(d\) 级祖先。
\(f(x,i)\) 表示以 \(x\) 为根的子树中,距离 \(x\) 为 \(i\) 的点数
\(g(x,i)\) 表示以 \(x\) 为根的子树中, 形如下图的的 \((a,b)\) 数量
其中 \(d\) 可以是任意值(说白了就是都考虑进去),或者说只要在上面街上一个长度为 \(i\) 的边就可以构成一个合法的三元组。
考虑转移,枚举 \(x\) 的下一个儿子 \(u\)
\(g(x,i) += f(x,i)\times f(u,i-1) + g(u,i+1)\)
\(f(x,i) += f(u,i-1)\)
4.线段树 [POI2015]KIN 看电影
10.5
1.旅行者和火把问题,首先贪心有两种方式,一种是每次最快的运一个人再回来,第二种是前两个人先过去,最快的送火把回来,然后两个最慢的过去,次快的再回来,这样是运两个人,动态规划维护这个贪心,设\(f[x]\)是从最后运到x位置的最小代价
第一种方案:\(a[1]+a[i]+f[i+1]\)
第二种方案:\(f[i+2]+a[1]+2*a[2]+a[i+1]\)
所以方程:\(f[i]=min(a[1]+a[i]+f[i+1],f[i+2]+a[1]+2*a[2]+a[i+1]);\)
注意特判,因为给出的按升序排列,所以当\(n<=2\)时直接输出\(a[n]\).
2.动态规划,
设\(g[l][r][i][j]\)为将l~r删至剩下的数的最小值为i,最大值为j,的最小代价。
\(f[l][r]\)为将l~r的序列全部删掉的最小总代价。
可以离散化一下
\(g[l][r][min(i,w[r])][max(j,w[r])]=g[l][r-1][i][j]\) 和前面的l~r-1放到一起删。
\(g[l][r][i][j]=min(g[l][r][i][j],g[l][k][i][j]+f[k+1][r])\) 和后面的k+1~r作为被包含子区间删去。
\(f[l][r]=g[l][r][i][j]+a+b*(j-i)^2\)
3.动态DP,
这个DP在树上进行,考虑轻重链剖分,设\(f[i]\)为以i 为跟的字数满足条件的最小总代价
\(f[i]=\text{min}(v[i],\sum\limits_{i\to j}f[j])\)
由于需要DDP,所以转化为
设y 为x的重儿子,x的所有轻儿子的f之和为g[x],则有\(f[x]=\text{min}(v[x],f[y]+g[x])\)
接下来可以用矩阵转移这个东西,每个叶子结点的代价已知
0 f[u] * 0 a[x] = 0 f[x]
\(\infty\) g[x]
第二行的转移结果不考虑
10.6
10.7
1.每次选三个,直接枚举所有情况,看哪个更优
2.概率DP
3.开一个bitset即可
代码:
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<bitset>
#define N 10005
#define M 100005
using namespace std;
bitset<N> to[N];
bitset<N> vis,quan;
int f[N][N],a[N],n,m,ans=2147483647;
int main()
{
n=read(); m=read();
for(int i=1;i<=n;i++)quan[i]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)f[i][j]=2147483647;
int x,y,c;
for(int i=1;i<=m;i++)
{
x=read();y=read();c=read();
if(f[x][y]==2147483647)a[x]++;
to[x][y]=1;
f[x][y]=min(f[x][y],c);
}
int mx;
for(int i=1;i<n;i++)
for(int j=i;j<=n;j++)
{
mx=0;
if(a[i]+a[j] < n)continue;
vis=to[i]|to[j];
if(vis != quan)continue;
for(int k=1;k<=n;k++) mx=max(mx,min(f[i][k],f[j][k]));
if(mx)ans=min(ans,mx);
}
if(ans==2147483647)puts("No solution");
else printf("%d\n",ans);
}
/*
4 13
1 1 1
1 2 3
1 3 3
1 4 5
2 1 2
2 2 1
2 3 2
3 1 4
3 3 4
3 4 1
4 1 2
4 2 3
4 4 3
*/
对拍
while( ((double)clock()-yilr) / CLOCKS_PER_SEC < 0.75) dfs(1,0,++dep);
CLOCKS_PER_SEC 这个常数在Windows下等于1000
迭代加深搜会用
对拍程序:
#include<cstdio>
#include<ctime>
#include<iostream>
#include<cstdlib>
using namespace std;
int main()
{
int T=0;
while(1)
{
double t1=clock();
system("F:\\random.exe");
double t2=clock();
system("F:\\baoli.exe");
double t3=clock();
system("F:\\sol.exe");
if(system("fc F:\\baoli.out F:\\sol.out"))
{
puts("yelir doesn't love you");
break;
}
else
printf("yelir love you,测试点 %d 总时间 %.0f",++T,(clock()-t1)/1000.0 );
}
return 0;
}
#include<cstdlib>
#include<cstdio>
#include<ctime>
using namespace std;
int main()
{
int T = 0;
while(1)
{
double ti = clock();
system("F:\\random.exe");
double st = clock();
system("F:\\baoli.exe");
double ed = clock();
system("F:\\sol.exe");
if (system("fc F:\\baoli.out F:\\sol.out")) {
puts("Wrong Answer");
return 0;
}
else
{
printf("Accepted, 测试点 #%d, 用时 %.0lfms 总用时 %.0lfs\n", ++ T, ed - st, (clock() - ti) / 1000);
}
}
}
各种排序
稳定的排序:
插入排序(直接插,二分插),冒泡排序,归并排序,基数排序,
不稳定的排序:
希尔排序,堆排序,快速排序,选择排序
yelir
1.注意撞关键字,比如next, end,y1,y2,x1,x2,j1,j2
2.注意超空间,时间问题,卡评测要专业
qsing2
1.是一道物理题,分三种情况,从高到低,从低到高,平跳,推式子即可
2.回声也就说明重复,可证最多重复一千次,然后建一棵Tire树,每次加入前查询,不必配直接返回即可。
3.线段树,当前点若没有人则赋为inf,单点修改,最后一问,可维护一下区间有多少学生,线段树区间求和即可,注意最后要从tail向前扫,因为他到队尾后还可能向队尾走,而且不一定总的队长为n+m,可能更长,而且注意build传参的范围,不是n,而是n+m.
#define N 20050
using namespace std;
int tr[20005005][2];
char s[2000];
int ans,tot,n;
inline void Insert(){
int k = 0;
for(int i = 0;i <= 1000;i ++){
int o = s[i] - '0';
if(!tr[k][o]) tr[k][o] = ++ tot;
k = tr[k][o];
}
}
inline int Ask() {
int k = 0,res = 0;
for(int i = 0;i <= 1000;i ++) {
int o = s[i] - '0';
if(!tr[k][o]) {
return res;
}
else k = tr[k][o],res ++;
}
return res;
}
int main() {
scanf("%d",&n);
for(int i = 1;i <= n;i ++) {
scanf("%s",s);
int len = strlen(s),cnt = 0;
for(int j = len;j <= 1000;j ++)
s[j] = s[cnt],cnt = (cnt + 1) % len;
ans = max(ans,Ask()); Insert();
}
printf("%d\n",ans);
fclose(stdin); fclose(stdout);
return 0;
}