2022.2.25 补题笔记
SDWC 2.12 T2
这个题按理说应该是比较好想的,可惜我 T1 突然脑血栓不知道如何维护次小值,导致这个题目不知道怎么做。
容易发现限制,向上的上面只能放上面,左边放向左或向上,右边放向右或向上,所以限制就能明显了,整张图相当于上面向上,左边向左,右边向右,下面向下,并且两条主对角线上的限制是相互独立的。注意到每个格子上的限制相当于某个格子必须被包含或者必须不被包含,我们只需要分别 dp 两条对角线,然后在过程中将不合法的值清零就可以。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 2010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=998244353;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int f[N][N],n,ans1,ans2;
char s[N][N];
bool v1[N][N],v2[N][N];
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);
for(int i=1;i<=n;i++){
scanf("%s",s[i]+1);
}
for(int i=1;i<=n;i+=2)
for(int j=1;j<=n;j+=2){
if(s[i][j+1]=='.') v1[i/2+1][j/2+1]=1;
if(s[i+1][j]=='.') v2[i/2+1][j/2+1]=1;
}
// printf("%d\n",v2[1][1]);
f[0][1]=1;
for(int j=1;j<=n/2+1;j++){
for(int i=1;i<=n/2+1;i++){
f[i][j]=(f[i][j-1]+f[i-1][j])%mod;
// printf("f[%d][%d]=%d\n",i,j,f[i][j]);
}
int w=-1;
for(int i=n/2;i>=1;i--){
if(v1[i][j]){w=i;break;}
}
// printf("w=%d\n",w);
if(w!=-1) for(int i=w;i>=1;i--)f[i][j]=0;
w=-1;
for(int i=1;i<=n/2;i++){
if(v2[i][j]){w=i;break;}
}
// printf("w=%d\n",w);
if(w!=-1) for(int i=w+1;i<=n/2+1;i++) f[i][j]=0;
}
ans1=f[n/2+1][n/2+1];
// printf("%d\n",ans1);
memset(v1,0,sizeof(v1));
memset(v2,0,sizeof(v2));
memset(f,0,sizeof(f));
for(int i=1;i<=n;i+=2)
for(int j=1;j<=n;j+=2){
if(s[i][j]=='.'){
v1[i/2+1][j/2+1]=1;
// printf("Must %d %d\n",i/2+1,j/2+1);
}
if(s[i+1][j+1]=='.'){
v2[i/2+1][j/2+1]=1;
// printf("Ban %d %d\n",i/2+1,j/2+1);
}
}
f[n/2+2][1]=1;
for(int j=1;j<=n/2+1;j++){
for(int i=n/2+1;i>=1;i--){
f[i][j]=(f[i+1][j]+f[i][j-1])%mod;
// printf("f[%d][%d]=%d\n",i,j,f[i][j]);
}
int w=-1;
for(int i=n/2;i>=1;i--){
if(v1[i][j]){w=i;break;}
}
// printf("w=%d\n",w);
if(w!=-1) for(int i=w;i>=1;i--) f[i][j]=0;
w=-1;
for(int i=1;i<=n/2;i++){
if(v2[i][j]){w=i;break;}
}
// printf("w=%d\n",w);
if(w!=-1) for(int i=w+1;i<=n/2+1;i++) f[i][j]=0;
}
ans2=f[1][n/2+1];
// printf("%d\n",ans2);
printf("%d\n",1ll*ans1*ans2%mod);
return 0;
}
CF1540B
对每个逆序对分开计算贡献,可以枚举根在根处点上第一滴墨。
AT2390
这个题目注意考虑 SG 函数对边的限制,然后可以通过 dp 来解决。注意子问题的划分。
AT3962
这个题目注意转化限制,限制可以被转化成非常简单的形式,然后考虑 dp,注意有那些东西要记录到状态里,即是那些能够对答案有影响的变量,转移的时候考虑吧添加操作变成删除操作,写出转移式子,前缀和优化。
CF830D
首先借助点分治的思想,考虑经过根节点的所有路径条数,由此可以把所有路径进行归类,考虑计算出 \(f_n\) 表示经过节点 \(n\) 的路径条数,发现需要知道某个子树内不交路径个数为 \(k\) 的条数,而深度相同的子树方案数相同,于是我们可以进行 dp,第一维是深度,第二维是路径条数。
AT2371
利用代数推导把一个 1D1D 形式的 DP 改成一个可以进行递推的 dp,具体方法是寻找 \(f_{i+1}\) 和 \(f_i\) 两个递推式之间的关系,从而可以通过矩阵快速幂来解决。
另一种方法是通过探求组合意义,转换 DP 形式,非 1D1D 形式的 DP 方便矩阵优化。
只有为什么考虑矩阵优化,因为 \(n\) 是 \(10^9\) 级别的。
P6944
首先经过计算发现每一种状态的方案数是一样的,然后我们可以用搭建楼梯的方式去 dp 每一种状态的方案和每一种状态的所有方案之和,注意如果答案过大要求精度需要用 Long double,还有一点要注意的是最后加上 r 是因为 dp 的时候最开始每个人手中的宝石并没有被考虑。
AT3860
如果超 longlong 了,可以选择用 long double 来存一个整数。
首先,我们可以认为所有线段的小数位不一样,如果一样的话,概率是非常小,接近为 0 的,既然不一样,那么肯定就会有谁大谁小,由于我们线段长度是整数,所以端点的分数部分相同,而我们只关心分数部分的大小关系,所以我们可以直接枚举分数的大小关系,具体做法就是 \(n!\) 去枚举所有线段。
我们考虑用最长的一段的开始位置断环为链,改成覆盖问题,因为小数部分被我们离散化了,所以相当于我们每一个单位长度里面有 \(n\) 个坐标,然后剩下的就是一个简单的状压 DP。
LOJ 6357
这个题目我们可以列出方程来,容易证明当两个数对于最大公约数同余是在一个环内,手玩高斯消元就可以。
一个比较妙的地方是关于标号的处理,转移方程式列在下方,我们可以通过把叫号位置后面的数减 \(1\) 的到新的标号,这样,标号为 \(0\) 的位置永远是 \(0\),转移方程体现了这种标号方式: