T 1 T_1 T1——matrix(3118)
Description:
在一个
n
∗
m
n*m
n∗m的只包含0,1的矩形中,求有多少个特殊的子矩形,特殊的字矩形满足
它的四条边上都是1,内部的0,1个数差不超过1,大小至少为2*2。
n
,
m
≤
300
n,m\le300
n,m≤300
Solution:
- 对于找一个字矩形的问题,我们可以先尝试地枚举一条边,作为长,再枚举一下宽。这样就是 Θ ( n 2 m ) \Theta(n^2m) Θ(n2m)。
- 那么剩下的判定一定是 Θ ( 1 ) \Theta(1) Θ(1)的,即用前缀和维护该边1的个数。
- 但是关键的条件是内部的0,1个数差不超过1,其实也就是在枚举宽的同时,维护一个0,1个数差的前缀,接着记录每个的0,1个数差的出现次数。
- 那么统计答案就可以由差为-1,0,1的状态转移即可。
Code:
#include<bits/stdc++.h>
using namespace std;
#define REP(i,f,t) for(int i=(f),i##_end_=(t);i<=i##_end_;++i)
#define ll long long
const int N=302,M=90000;
int n,m;
int A[N][N];
int col[N][N];
int cnt[M*2+5],mark[M*2+5];
int main(){
// freopen("matrix.in","r",stdin);
// freopen("matrix.out","w",stdout);
scanf("%d%d",&n,&m);
REP(i,1,n) REP(j,1,m) scanf("%d",&A[i][j]),col[i][j]=col[i-1][j]+A[i][j];
int ans=0;
REP(i,1,n) REP(j,i+1,n){
int tot=0,sum=0,len=j-i+1;
REP(k,1,m){
int res=(len-2)-2*(col[j-1][k]-col[i][k]);
if(len==col[j][k]-col[i-1][k]){
ans+=cnt[sum+M]+cnt[sum+M-1]+cnt[sum+M+1];
cnt[sum+M+res]++;
mark[++tot]=sum+M+res;
}
if(!A[j][k] || !A[i][k]) {
REP(l,1,tot)cnt[mark[l]]=0;
tot=0;
}
sum+=res;
}
REP(l,1,tot) cnt[mark[l]]=0;
}
printf("%d\n",ans);
return 0;
}
T 2 T_2 T2——build(2960)
Description:
一个有
n
n
n个格子的带子,每个格子可以涂成黑色或白色,而对于一条涂好的带子,对于每一个格子,它的花费为与之不同颜色的最近的距离和当前格子的颜色权值的乘积,即
c
o
s
t
i
=
d
i
s
i
∗
v
a
l
[
i
]
[
c
o
l
i
]
,
c
o
l
i
cost_i=dis_i*val[i][col_i],col_i
costi=disi∗val[i][coli],coli为0或1,表示黑白。
求这带子的最小花费。
n
≤
4000
,
v
a
l
[
i
]
[
0
/
1
]
≤
1
0
5
n\le4000,val[i][0/1]\le10^5
n≤4000,val[i][0/1]≤105
Solution:
- 此题关键在于对于每个格子的与它不同颜色的格子的最近距离很难确定,或者说找到最优的。
- 那么我们就定义一个准确的方向,使得一些格子的最近距离一定在它的左边或右边,即分治。
- 我们可以划分两个区间 [ L , m i d ] , [ m i d + 1 , R ] [L,mid],[mid+1,R] [L,mid],[mid+1,R],使区间 [ L , m i d ] [L,mid] [L,mid]的格子的最近距离都在它的右边,区间 [ m i d + 1 , R ] [mid+1,R] [mid+1,R]的格子的最近距离都在它的左边。
- 同样是通过 d p dp dp来定义状态来找到最后的最优解。
- 即 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1]表示前 i i i个格子,到第 i i i个格子是,它的状态为黑色或白色的最优解。
- 那么转移就是通过刚刚的分治思想,我们就需要预处理出对于每格子向左或向右走 k k k步的花费。
Code:
#include<bits/stdc++.h>
using namespace std;
#define REP(i,f,t) for(int i=(f),i##_end_=(t);i<=i##_end_;++i)
#define SREP(i,f,t) for(int i=(f),i##_end_=(t);i<i##_end_;++i)
#define DREP(i,f,t) for(int i=(f),i##_end_=(t);i>=i##_end_;--i)
#define ll long long
template<class T>inline bool chkmin(T &x,T y){return x>y?x=y,1:0;}
const int N=4010;
int n;
struct node{
int a,b;
}A[N];
struct p20{
void solve(){
SREP(i,0,n) scanf("%d%d",&A[i].a,&A[i].b);
ll ans=0x3f3f3f3f;
int t[11];
SREP(s,1,(1<<n)-1){
memset(t,0,sizeof(t));
SREP(i,0,n) if(s&(1<<i)) t[i]=1;
ll res=0;
SREP(i,0,n){
if(t[i]){
REP(k,1,n) {
if(i-k>=0 && !t[i-k]) {res+=k*A[i].b;break;}
if(i+k<n && !t[i+k]) {res+=k*A[i].b;break;}
}
}
else {
REP(k,1,n){
if(i-k>=0 && t[i-k]) {res+=k*A[i].a;break;}
if(i+k<n && t[i+k]) {res+=k*A[i].a;break;}
}
}
}
chkmin(ans,res);
}
printf("%lld\n",ans);
}
}p1;
struct p100{
int A[2][N];
ll L[2][N],R[2][N],sum[2][N];
ll dp[2][N];
void solve(){
memset(dp,-1,sizeof(dp));
REP(i,1,n) scanf("%d%d",&A[0][i],&A[1][i]);
SREP(i,0,2){
REP(j,1,n) sum[i][j]=sum[i][j-1]+A[i][j];
REP(j,1,n) L[i][j]=L[i][j-1]+j*A[i][j];
DREP(j,n,1) R[i][j]=R[i][j+1]+(n+1-j)*A[i][j];
}
dp[0][0]=dp[1][0]=0;
REP(i,1,n) SREP(j,0,i) SREP(k,0,2){
ll res=0;
int l=j+1,r=i;
int mid=(j+i+1)>>1;
if(j+1==1 && i==n) res=1e17;
else if(j+1==1) res=R[!k][l]-R[!k][r+1]-(sum[!k][r]-sum[!k][l-1])*(n-r);
else if(r==n) res=L[!k][r]-L[!k][l-1]-(sum[!k][r]-sum[!k][l-1])*(l-1);
else res=L[!k][mid]-L[!k][l-1]-(sum[!k][mid]-sum[!k][l-1])*(l-1)+R[!k][mid+1]-R[!k][r+1]-(sum[!k][r]-sum[!k][mid])*(n-r);
if(~dp[k][i]) chkmin(dp[k][i],dp[!k][j]+res);
else dp[k][i]=dp[!k][j]+res;
}
printf("%lld\n",min(dp[0][n],dp[1][n]));
}
}p2;
int main(){
// freopen("build.in","r",stdin);
// freopen("build.out","w",stdout);
scanf("%d",&n);
if(n<=10) p1.solve();
else p2.solve();
return 0;
}
T 3 T_3 T3——network(3111)
Description:
有一棵
n
n
n个节点的树,每个节点有一个
v
a
l
i
val_i
vali,求出所有
v
a
l
val
val互质的节点的距离和。
n
,
v
a
l
i
≤
1
0
5
n,val_i\le10^5
n,vali≤105
Solution:
- 一道非常传统的数据结构题,首先对于互质,无非是要通过容斥或莫比乌斯反演来统计答案,而关键就是怎样知道之间距离,发现复杂度是不够的。
- 这里引入一个新知识——虚树。
- 虚树的作用即保留原树上有用的节点,期望虚树的大小为 log n \log n logn,这样就能达到一个 log \log log,而接下来就是暴力莫比乌斯反演,而 1 0 5 10^5 105的质因数只有8个,因子只有128个,那么复杂度为 Θ ( n log n ∗ 64 ) \Theta(n\log n*64) Θ(nlogn∗64)
Code:
#include<bits/stdc++.h>
using namespace std;
#define REP(i,f,t) for(int i=(f),i##_end_=(t);i<=i##_end_;++i)
#define SREP(i,f,t) for(int i=(f),i##_end_=(t);i<i##_end_;++i)
#define DREP(i,f,t) for(int i=(f),i##_end_=(t);i>=i##_end_;--i)
#define ll long long
template<class T>inline bool chkmin(T &x,T y){return x>y?x=y,1:0;}
template<class T>inline bool chkmax(T &x,T y){return x<y?x=y,1:0;}
template<class T>inline void Rd(T &x){
x=0;char c;
while((c=getchar())<48);
do x=(x<<1)+(x<<3)+(c^48);
while((c=getchar())>47);
}
const int N=1e5+2;
int n;
int val[N];
int head[N],qwq;
struct edge{
int to,nxt;
}E[N<<1];
void addedge(int x,int y){E[qwq]=(edge){y,head[x]};head[x]=qwq++;}
int Mx;
int fa[N],dep[N],sz[N],son[N],top[N];
struct p30{
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
void dfs1(int x,int f){
fa[x]=f;
dep[x]=dep[f]+1;
sz[x]=1;
for(int i=head[x];~i;i=E[i].nxt){
int y=E[i].to;
if(y==f)continue;
dfs1(y,x);
sz[x]+=sz[y];
if(sz[son[x]]<sz[y])son[x]=y;
}
}
void dfs2(int x,int tp){
top[x]=tp;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];~i;i=E[i].nxt){
int y=E[i].to;
if(y==fa[x] || y==son[x]) continue;
dfs2(y,y);
}
}
int Lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
void solve(){
dfs1(1,0);
dfs2(1,1);
ll ans=0;
REP(i,1,n) REP(j,i+1,n) if(gcd(val[i],val[j])==1){
int lca=Lca(i,j);
ans+=dep[i]+dep[j]-2*dep[lca];
}
printf("%lld\n",ans);
}
}p1;
struct p60{
#define NN 10002
int c[NN][505],cnt1[505],cnt2[505];
ll dp[NN][505],ans;
vector<int>prime[505];
void Init(){
REP(i,2,500){
int x=i;
for(int j=2;j<=x;++j){
if(x%j==0){
prime[i].push_back(j);
while(x%j==0)x/=j;
}
}
}
}
void calc(int x){
memset(cnt1,0,sizeof(cnt1));
memset(cnt2,0,sizeof(cnt2));
REP(i,1,Mx) for(int j=1;j*j<=i;++j) {
if(i%j)continue;
cnt1[j]+=dp[x][i];
cnt2[j]+=c[x][i];
if(i/j!=j) {
cnt1[i/j]+=dp[x][i];
cnt2[i/j]+=c[x][i];
}
}
}
void dfs(int x,int f){
for(int i=head[x];~i;i=E[i].nxt){
int y=E[i].to;
if(y==f)continue;
dfs(y,x);
ll sum1=0,sum2=0;
REP(j,1,Mx) sum1+=dp[x][j],sum2+=c[x][j];
calc(x);
REP(j,1,Mx){
if(!c[y][j])continue;
int l=(1<<(prime[j].size()));
ll tot1=0,tot2=0;
SREP(k,1,l){
int tmp=1,t1=0;
SREP(p,0,prime[j].size()) if(k&(1<<p))++t1,tmp*=prime[j][p];
if(t1&1)tot1+=cnt1[tmp],tot2+=cnt2[tmp];
else tot1-=cnt1[tmp],tot2-=cnt2[tmp];
}
ans+=(sum1-tot1)*c[y][j]+(sum2-tot2)*(dp[y][j]+c[y][j]);
}
REP(j,1,Mx){
dp[x][j]+=dp[y][j]+c[y][j];
c[x][j]+=c[y][j];
}
}
calc(x);
ll sum=0,tot=0;
REP(j,1,Mx)sum+=dp[x][j];
int l=(1<<(prime[val[x]].size()));
SREP(i,1,l){
int tmp=1,t1=0;
SREP(j,0,prime[val[x]].size()) if(i&(1<<j))++t1,tmp*=prime[val[x]][j];
if(t1&1)tot+=cnt1[tmp];
else tot-=cnt1[tmp];
}
ans+=(sum-tot);
c[x][val[x]]++;
}
void solve(){
Init();
dfs(1,0);
printf("%lld\n",ans);
}
}p2;
int dfn[N],lim[N],tim;
bool cmp(int x,int y){return dfn[x]<dfn[y];}
struct p100{
void dfs1(int x,int f){
dfn[x]=tim++;
fa[x]=f;
dep[x]=dep[f]+1;
sz[x]=1;
son[x]=0;
for(int i=head[x];~i;i=E[i].nxt){
int y=E[i].to;
if(y==f)continue;
dfs1(y,x);
sz[x]+=sz[y];
if(sz[son[x]]<sz[y]) son[x]=y;
}
lim[x]=tim;
}
void dfs2(int x,int tp){
top[x]=tp;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];~i;i=E[i].nxt){
int y=E[i].to;
if(y==fa[x] || y==son[x])continue;
dfs2(y,y);
}
}
int Lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int A[N],B[N],stk[N];
int Sz[N];
vector<int>T[N];
int mu[N];
ll calc(int k){
int len=0,top=0;
for(int i=k;i<N;i+=k) SREP(j,0,T[i].size()) A[len++]=T[i][j];
sort(A,A+len,cmp);
SREP(i,1,len) A[len++]=Lca(A[i-1],A[i]);
sort(A,A+len,cmp);
len=unique(A,A+len)-A;
SREP(i,0,len){
int x=A[i];
while(top and lim[stk[top-1]]<=dfn[x]) --top;
B[x]=stk[top-1];
stk[top++]=x;
}
ll res=0;
int tot=0;
SREP(i,0,len){
Sz[A[i]]=(val[A[i]]%k==0);
tot+=Sz[A[i]];
}
DREP(i,len-1,1){
int x=A[i],y=B[x];
res+=1ll*(dep[x]-dep[y])*Sz[x]*(tot-Sz[x]);
Sz[y]+=Sz[x];
}
return res;
}
void solve(){
dfs1(1,0),dfs2(1,1);
REP(i,1,n) T[val[i]].push_back(i);
ll ans=0;
mu[1]=1;
SREP(i,1,N) if(mu[i]) {
for(int j=i+i;j<N;j+=i) mu[j]-=mu[i];
ans+=calc(i)*mu[i];
}
printf("%lld\n",ans);
}
}p3;
int main(){
// freopen("network.in","r",stdin);
// freopen("network.out","w",stdout);
memset(head,-1,sizeof(head));
Rd(n);
REP(i,1,n) Rd(val[i]),chkmax(Mx,val[i]);
SREP(i,1,n){
int a,b;
Rd(a),Rd(b);
addedge(a,b);
addedge(b,a);
}
if(n<=500) p1.solve();
else if(Mx<=500) p2.solve();
else p3.solve();
return 0;
}
总结:
- 感觉大部分时间还是在磨 T 2 T_2 T2,对于 d p dp dp的转移还是太想当然了,有时候还需要借助其它思想或数据结构来维护,不仅仅是简单的转移。
- 对于虚树,感觉这又是一个暴力的数据结构,而关键建虚树是要非常明确的知道哪些是有用的点。