0 粉刷匠
windy有 N 条木板需要被粉刷。
每条木板被分为 M 个格子。
每个格子要被刷成红色或蓝色。
windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。
每个格子最多只能被粉刷一次。
如果windy只能粉刷 T 次,他最多能正确粉刷多少格子?
一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。
100%的数据,满足 1 <= N,M <= 50 ; 0 <= T <= 2500 。
————————————————————————————————————
每个格子最多只能被粉刷一次
很好没有后效性了,秒变水
相当于分组背包,先在组内DP,再做背包
对于每条木板:设
g
[
i
]
[
j
]
g[i][j]
g[i][j]表示第i个格子粉刷了j次的最大正确粉刷数;
g
[
i
]
[
j
]
=
m
a
x
(
g
[
i
−
k
]
[
j
−
1
]
+
m
a
x
(
r
e
d
[
r
]
−
r
e
d
[
r
−
l
]
,
b
l
u
e
[
r
]
−
b
l
u
e
[
r
−
l
]
)
)
g[i][j]=max(g[i-k][j-1]+max(red[r]-red[r-l],blue[r]-blue[r-l]))
g[i][j]=max(g[i−k][j−1]+max(red[r]−red[r−l],blue[r]−blue[r−l]))
对于所有木板:设
f
[
i
]
[
j
]
表
示
选
到
i
块
木
板
粉
刷
了
j
次
的
最
大
正
确
粉
刷
数
f[i][j]表示选到i块木板粉刷了j次的最大正确粉刷数
f[i][j]表示选到i块木板粉刷了j次的最大正确粉刷数
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
k
]
+
g
[
i
]
[
k
]
)
f[i][j]=max(f[i-1][j-k]+g[i][k])
f[i][j]=max(f[i−1][j−k]+g[i][k])
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m,t,s[60];
int h[60][60],f[2600][60];
int main(){
scanf("%d%d%d",&n,&m,&t);
for (int i=1;i<=n;i++){
char ch[60];
scanf("%s",ch+1);
memset(h,0,sizeof h);
for (int j=1;j<=m;j++){
s[j]=s[j-1]+ch[j]-'0';
for (int l=1;l<=min(j,t);l++)
for (int k=0;k<j;k++)
h[j][l]=max(h[j][l],max(h[k][l],h[k][l-1]+max(s[j]-s[k],j-k-s[j]+s[k])));
}
for (int j=0;j<=min(t,i*m);j++)
for (int k=0;k<=min(j,m);k++)
f[j][i]=max(f[j-k][i-1]+h[m][k],f[j][i]);
}
printf("%d",f[t][n]);
}
1 迷路
windy在有向图中迷路了。
该有向图有 N 个节点,windy从节点 0 出发,他必须恰好在 T 时刻到达节点 N-1。
现在给出该有向图,你能告诉windy总共有多少种不同的路径吗?
注意:windy不能在某个节点逗留,且通过某有向边的时间严格为给定的时间。
100%的数据,满足 2 <= N <= 10 ; 1 <= T <= 1000000000 。
你说什么?矩阵乘法?我学过,我不会[菜鸡呆滞.jpg]
什么?矩阵乘法快速幂?什么东西?[咸鱼呆滞.jpg]
矩阵乘法定义:a*b=c———》
c
[
i
]
[
j
]
=
a
[
i
]
[
k
]
+
b
[
k
]
[
j
]
c[i][j]=a[i][k]+b[k][j]
c[i][j]=a[i][k]+b[k][j]
其中a矩阵与b矩阵必有一边等长
矩阵乘法快速幂:将快速幂中的
a
b
a^b
ab的数a换成矩阵a,其中a的长宽需要相等,
O
(
n
3
l
o
g
b
)
O(n^3logb)
O(n3logb)
大部分矩阵乘法的题目都会用到快速幂
矩阵乘法可用于优化动态规划,这题用到的是矩阵乘法在邻接矩阵中的一个定义:
定义该邻接矩阵为一个图,a[i][j]表示i到j有边,
a
b
[
i
]
[
j
]
a^b[i][j]
ab[i][j]表示i在走b次后恰好到达j的方法数
哇好东西呐[无知的乡下蒟蒻呆滞.jpg]
在这道题中,边权较小,可以强行拆点然后矩阵乘法快速幂,
O
(
27
n
3
l
o
g
T
)
O(27n^3logT)
O(27n3logT)
拆点具体做法:
将编号为i点拆为编号为i * 9~i * 9+8的9个点
对于一条非0的边( i , j )= k ,
a
[
i
∗
9
+
k
−
1
]
[
j
∗
9
]
=
1
a[i*9+k-1][j*9]=1
a[i∗9+k−1][j∗9]=1,并将点
(
i
∗
9
,
j
∗
9
)
到
(
i
∗
9
+
k
−
1
,
j
∗
9
)
(i*9,j*9)到(i*9+k-1,j*9)
(i∗9,j∗9)到(i∗9+k−1,j∗9)的路径加入矩阵,相当于从点i走到点j经过了k条边权为1的点,总边权等价于原边权k
#include <cstdio>
#include <cstring>
using namespace std;
int n,t;
int a[125][125],b[125][125];
void read(){
scanf("%d%d",&n,&t);
for (int i=0;i<n;i++){
char ch[15];
scanf("%s",ch);
for (int j=0;j<n;j++)
if (ch[j]-'0'>0)
a[i*9+ch[j]-'0'-1][j*9]=1;
for (int j=0;j<8;j++)
a[i*9+j][i*9+j+1]=1;
}
}
void ksm(int t){
int c[125][125];
for (int i=0;i<=n*9;i++)
for (int j=0;j<=n*9;j++)
b[i][j]=a[i][j];
while (t){
if (t&1==1) {
memset(c,0,sizeof c);
for (int i=0;i<=n*9;i++)
for (int j=0;j<=n*9;j++)
for (int k=0;k<=n*9;k++)
c[i][j]=(c[i][j]+a[i][k]*b[k][j])%2009;
for (int i=0;i<=9*n;i++)
for (int j=0;j<=9*n;j++)
b[i][j]=c[i][j];
}
memset(c,0,sizeof c);
for (int i=0;i<=n*9;i++)
for (int j=0;j<=n*9;j++)
for (int k=0;k<=n*9;k++)
c[i][j]=(c[i][j]+a[i][k]*a[k][j])%2009;
for (int i=0;i<=9*n;i++)
for (int j=0;j<=9*n;j++)
a[i][j]=c[i][j];
t=t>>1;
}
}
int main(){
read();
ksm(t-1);
printf("%d",b[0][(n-1)*9]) ;
}
2 游戏
windy学会了一种游戏。 对于1到N这N个数字,都有唯一且不同的1到N的数字与之对应。 最开始windy把数字按顺序1,2,3,……,N写一排在纸上。 然后再在这一排下面写上它们对应的数字。 然后又在新的一排下面写上它们对应的数字。 如此反复,直到序列再次变为1,2,3,……,N。 如: 1 2 3 4 5 6 对应的关系为 1->2 2->3 3->1 4->5 5->4 6->6 windy的操作如下
1 2 3 4 5 6
2 3 1 5 4 6
3 1 2 4 5 6
12 3 5 4 6
2 3 1 4 5 6
3 1 2 5 4 6
1 2 3 4 5 6
这时,我们就有若干排1到N的排列,上例中有7排。 现在windy想知道,对于所有可能的对应关系,有多少种可能的排数。
100%的数据,满足 1 <= N <= 1000 。
没太明白题意?感觉题目描述和输出描述矛盾?
不管,事实上是按照题目描述来的,求可能的排数的种数
题意中有两种数量,排数和排数的种数
随便举一个大一点的n(比如10),再随便列一种对应关系,手动模拟过程,发现对应关系必定构成循环对应
比如1->2, 2->5, 5->1 就是一个大小为3的循环
在这样的一个循环中,循环它的大小n次后将回到正确对应(即每个数的位置是自己)
而一个n可能有多个不同的循环,排数就是这些循环大小的lcm(最小公倍数)
在同一种对应关系中,循环大小的和不超过n,而又因为可以用1来填充,所以可以不到n
排数lcm质因数分解后=
p
1
k
1
∗
p
2
k
2
∗
.
.
.
∗
p
n
k
n
p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n}
p1k1∗p2k2∗...∗pnkn
又有
p
1
k
1
+
p
2
k
2
+
.
.
.
+
p
n
k
n
<
=
n
p_1^{k_1}+p_2^{k_2}+...+p_n^{k_n}<=n
p1k1+p2k2+...+pnkn<=n
就转化为一个类似背包的东西,先筛质数,然后对每个质数的k次方( p i k < = n p_i^k<=n pik<=n)做背包,每种质数只能选一次
#include <cstdio>
#include <cstring>
using namespace std;
int n;
long long ans;
long long f[1050];
int b[1050],c[1050],cnt;
int main(){
scanf("%d",&n);
for (int i=2;i<=n;i++){
if (b[i]==0){
b[i]=1;
c[++cnt]=i;
}
for (int j=1;i*c[j]<=n&&j<=cnt;j++){
b[i*c[j]]=1;
if (i%c[j]==0) break;
}
}
f[0]=1;
for (int i=1;i<=cnt;i++)
for (int j=n;j>=c[i];j--)
for (int k=c[i];k<=j;k*=c[i])
f[j]+=f[j-k];
for (int i=0;i<=n;i++)
ans=(long long)ans+f[i];
printf("%lld",ans);
}
3 windy数
windy定义了一种windy数。
不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。
windy想知道,在A和B之间,包括A和B,总共有多少个windy数?
100%的数据,满足 1 <= A <= B <= 2000000000 。
这种l和r之间有多少个xxx的一般转化为1–n有多少的xxx,ans=ans[r]-ans[l]
然后这个满足某某条件的xx数就用数位DP求
设 f [ i ] [ j ] f[i][j] f[i][j]表示第i个数字,最后一个数字为j有多少个xx数
统计答案时,先把位数小于n的加上,再把位数等于n但是首数字小于n的加上
然后枚举第一个数字、第二个数字~最后一个数字等于n的加上
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a,b;
int f[20][20];
int read(){
int x=0;
char ch;
ch=getchar();
while (ch<'0'||ch>'9') ch=getchar();
while (ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
void dp(){
for (int i=0;i<=9;i++) f[1][i]=1;
for (int i=2;i<=10;i++)
for (int j=0;j<=9;j++)
for (int k=0;k<=9;k++)
if (abs(j-k)>=2)
f[i][j]+=f[i-1][k];
}
int work(int x){
int s[20],ans=0;
memset(s,0,sizeof s);
while (x){
s[++s[0]]=x%10;
x/=10;
}
for (int i=1;i<s[0];i++)
for (int j=1;j<=9;j++)
ans+=f[i][j];
for (int i=1;i<s[s[0]];i++)
ans+=f[s[0]][i];
for (int i=s[0]-1;i>=1;i--){
for (int j=0;j<s[i];j++){
if (abs(j-s[i+1])>=2)
ans+=f[i][j];
}
if (abs(s[i+1]-s[i])<2) break;
}
return ans;
}
int main(){
a=read(),b=read();
dp();
printf("%d",work(b+1)-work(a));
}