矩阵填数
一、题目
二、解法
观察数组范围,发现
h
,
w
h,w
h,w特别大,而限制只有至多
10
10
10条,我们考虑离散化。
怎么个离散化法呢?考虑将每一个限制的四个边延长,这样就将大矩形切割成了至多
20
×
20
20\times 20
20×20个小矩形,且这些小矩形的性质是一样的(被哪些限制包含)。
具体的离散化操作我们把它当做切木棍,对于
[
l
,
r
]
[l,r]
[l,r],我们这样离散化:
也就是我们把
l
,
r
l,r
l,r各切一刀,在
l
l
l加上一个左括号,
l
−
1
l-1
l−1加上一个右括号,
r
r
r加上一个左括号,
r
+
1
r+1
r+1加上一个右括号,离散化就变成了括号匹配问题,排序后匹配相邻的括号即可。
离散化后,我们发现每个限制中只要中只要有一个小矩形满足限制就可以了,由于限制数很小,考虑状态压缩,设
d
p
[
i
]
[
s
]
dp[i][s]
dp[i][s]为前
i
i
i个小矩形满足状压后为
s
s
s的条件的方案数,我们先考虑每一个小矩形:
中间的那个小矩形被两个限制所包含,但是我们只能满足要求最大值为
3
3
3的限制,所以我们记小矩形的状态为
10
10
10(二进制),
d
p
dp
dp时拿原状态或上小矩形的状态就可以得到目标状态。我们再记
g
[
i
]
=
(
M
i
n
−
1
)
s
g[i]=(Min-1)^s
g[i]=(Min−1)s(
s
s
s为小矩形的面积),即无法满足任何一个限制的情况,
f
[
i
]
=
M
i
n
s
−
g
[
i
]
f[i]=Min^{s}-g[i]
f[i]=Mins−g[i]为能满足最大值最小的限制的情况(我们也只能满足这些限制),那样我们就能写出如下
d
p
dp
dp方程(记小矩形状态为
S
S
S):
{
d
p
[
i
]
[
j
∣
S
]
=
d
p
[
i
−
1
]
[
j
]
×
f
[
i
]
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
×
g
[
i
]
\begin{cases} dp[i][j|S]=dp[i-1][j]\times f[i] \\ dp[i][j]=dp[i-1][j]\times g[i] \end{cases}
{dp[i][j∣S]=dp[i−1][j]×f[i]dp[i][j]=dp[i−1][j]×g[i]
最后输出
d
p
[
m
]
[
2
n
−
1
]
dp[m][2^n-1]
dp[m][2n−1]即可(
m
m
m是小矩形的个数),时间复杂度
O
(
4
n
2
×
2
n
)
O(4n^{2}\times 2^n)
O(4n2×2n)。
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int MOD = 1e9+7;
const int MAXN = 10005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int T,h,w,m,n,cnt,oth,dp[505][1<<10];
int x1[MAXN],y1[MAXN],x2[MAXN],y2[MAXN],v[MAXN];
bool vx[MAXN][2],vy[MAXN][2];
vector<pair<int,int> > x,y;
struct node
{
int f,g,s;
}a[505];
void add(int z,bool d,bool type)
{
if(!type) x.push_back(make_pair(z,d)),vx[z][d]=1;
else y.push_back(make_pair(z,d)),vy[z][d]=1;
}
int qkpow(int a,int b)
{
int res=1;
while(b>0)
{
if(b&1) res=res*a%MOD;
a=a*a%MOD;
b>>=1;
}
return res;
}
signed main()
{
T=read();
while(T--)
{
h=read();w=read();m=read();n=read();
cnt=oth=0;
memset(vx,0,sizeof vx);
memset(vy,0,sizeof vy);
memset(dp,0,sizeof dp);
x.clear();y.clear();
add(1,0,0);add(h,1,0);
add(1,0,1);add(w,1,1);
for(int i=1;i<=n;i++)
{
x1[i]=read();y1[i]=read();x2[i]=read();y2[i]=read();v[i]=read();
if(x1[i]>1 && !vx[x1[i]-1][1])
add(x1[i]-1,1,0);
if(!vx[x1[i]][0])
add(x1[i],0,0);
if(x2[i]<h && !vx[x2[i]+1][0])
add(x2[i]+1,0,0);
if(!vx[x2[i]][1])
add(x2[i],1,0);
if(y1[i]>1 && !vy[y1[i]-1][1])
add(y1[i]-1,1,1);
if(!vy[y1[i]][0])
add(y1[i],0,1);
if(y2[i]<w && !vy[y2[i]+1][0])
add(y2[i]+1,0,1);
if(!vy[y2[i]][1])
add(y2[i],1,1);
}
sort(x.begin(),x.end());
sort(y.begin(),y.end());
for(int i=0;i<x.size();i+=2)
for(int j=0;j<y.size();j+=2)
{
int a1=x[i].first,a2=x[i+1].first;
int b1=y[j].first,b2=y[j+1].first;
int siz=(a2-a1+1)*(b2-b1+1),s=0,Min=m;
for(int k=1;k<=n;k++)
{
if(a1>=x1[k] && a2<=x2[k] && b1>=y1[k] && b2<=y2[k])
{
if(Min>v[k])
{
Min=v[k];
s=0;
}
if(Min==v[k]) s|=(1<<k-1);
}
}
int g=qkpow(Min-1,siz),f=qkpow(Min,siz)-g;
if(!s) oth+=siz;
else a[++cnt]=node{f,g,s};
}
dp[0][0]=1;
for(int i=1;i<=cnt;i++)
{
for(int j=0;j<(1<<n);j++)
dp[i][j|a[i].s]=(dp[i][j|a[i].s]+dp[i-1][j]*a[i].f)%MOD;
for(int j=0;j<(1<<n);j++)
dp[i][j]=(dp[i][j]+dp[i-1][j]*a[i].g)%MOD;
}
printf("%d\n",(dp[cnt][(1<<n)-1]*qkpow(m,oth)%MOD+MOD)%MOD);
}
}
魔方俱乐部
一、题目
二、解法
缩点之后无脑 d f s dfs dfs,不展开。
#include <cstdio>
#include <vector>
#include <cstring>
#include <stack>
using namespace std;
const int MAXN = 200005;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*flag;
}
int n,tot,sum,a[MAXN],f[MAXN],val[MAXN],in[MAXN];
int Index,cnt,dfn[MAXN],low[MAXN],mark[MAXN],ans[MAXN];
bool vis[MAXN];
stack<int> s;
vector<int> G[MAXN];
struct edge
{
int v,next;
} e[MAXN];
void dfs(int u)
{
dfn[u]=low[u]=++Index;
in[u]=1;
s.push(u);
for(int i=f[u]; i; i=e[i].next)
{
int v=e[i].v;
if(!dfn[v])
{
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
int v;
cnt++;
do
{
v=s.top();
in[v]=0;
mark[v]=cnt;
s.pop();
}
while(u!=v);
}
}
void count(int u)
{
vis[u]=1;
ans[u]+=val[u];
for(int i=0; i<G[u].size(); i++)
{
int v=G[u][i];
if(!vis[v])
count(v);
ans[u]+=ans[v];
}
}
int main()
{
n=read();
for(int i=1; i<=n; i++)
a[i]=read();
for(int i=1; i<=n; i++)
{
int j=read();
e[++tot]=edge{j,f[i]},f[i]=tot;
}
for(int i=1; i<=n; i++)
if(!dfn[i])
dfs(i);
for(int i=1; i<=n; i++)
{
val[mark[i]]+=a[i];
for(int j=f[i]; j; j=e[j].next)
{
int v=e[i].v;
if(mark[i]!=mark[v])
{
in[mark[v]]++;
G[mark[i]].push_back(mark[v]);
}
}
}
for(int i=1; i<=cnt; i++)
{
if(!in[i])
count(i);
}
for(int i=1; i<=n; i++)
printf("%d\n",ans[mark[i]]);
}
手机号码
一、题目
二、解法
以前基本上没有写过数位
d
p
dp
dp的题,今天来先学一下吧。
数位
d
p
dp
dp的基本思路就是先把
d
f
s
dfs
dfs版的爆搜打出来,然后套个记忆化就
A
A
A了。
说着感觉特别简单,实际上也很简单。
对于本题,定义
d
p
[
i
]
[
s
0
]
[
s
1
]
[
s
2
]
[
s
3
]
[
p
r
e
]
dp[i][s_0][s_1][s_2][s_3][pre]
dp[i][s0][s1][s2][s3][pre]表示当前是第
i
i
i位,
s
0
s_0
s0:是否紧贴着数值限制,
s
1
s_1
s1:是否有三连等,
s
2
s_2
s2:是否有二连等,
s
3
s_3
s3:
4
,
8
4,8
4,8出现没有的状态压缩,
p
r
e
pre
pre:上一个数,转移并不难。
怎么算时间复杂度呢?把状态数
×
\times
×转移次数(因为是记忆化搜索),就得到了时间复杂度。
#include <cstdio>
#include <cstring>
#define int long long
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=x*10+c-'0',c=getchar();
return x*flag;
}
int L,R,num[12],init[10]= {0,0,0,0,1,0,0,0,2,0};
int dp[12][2][2][2][4][11];
int dfs(int pos,bool equal,bool tri,bool bi,int lck,int pre)
{
if(lck==3) return 0;
if(pos==0) return tri;
if(dp[pos][equal][tri][bi][lck][pre]!=-1) return dp[pos][equal][tri][bi][lck][pre];
int t=0;
for(int i=0; i<10; i++)
{
if(equal && i>num[pos]) break;
t+=dfs(pos-1,equal&(i==num[pos]),tri||(bi&(pre==i)),pre==i,lck|init[i],i);
}
return dp[pos][equal][tri][bi][lck][pre]=t;
}
int get(int x)
{
memset(dp,-1,sizeof dp);
memset(num,0,sizeof num);
int t=0,ret=0;
while(x>0) num[++t]=x%10,x/=10;
for(int i=1; i<=num[11]; i++)
{
ret+=dfs(10,i==num[11],0,0,init[i],i);
}
return ret;
}
signed main()
{
L=read();
R=read();
printf("%lld\n",get(R)-get(L-1));
}