洛谷 3084 照片 Photo
题目
在长度为 n n n的线段中给出若干个区间,询问是否存在一种情况使最多的点在每个区间中有且仅出现一次
分析
设
d
p
[
i
]
dp[i]
dp[i]表示前
i
i
i个点第
i
i
i个点必选所能产生的最多的点,那么显然在后面要开一个虚拟点,因为最后一个点不必选
那么记录该点所能被推导的最小区间,用单调队列维护dp,时间复杂度
O
(
n
)
O(n)
O(n)
代码
#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
const int N=200101;
int n,l[N],r[N],f[N],q[N];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
signed main(){
n=iut();
for (rr int i=1;i<=n+1;++i) r[i]=i-1;
for (rr int m=iut();m;--m){
rr int x=iut(),y=iut();
r[y]=min(r[y],x-1);
l[y+1]=max(l[y+1],x);
}
for (rr int i=n;i;--i) r[i]=min(r[i],r[i+1]);
for (rr int i=1;i<=n+1;++i) l[i]=max(l[i],l[i-1]);
rr int head=1,tail=1; q[1]=0;
for (rr int i=1,j=1;i<=n+1;++i){
for (;j<=r[i]&&j<=n;++j) if (~f[j]){
while (head<=tail&&f[q[tail]]<f[j]) --tail;
q[++tail]=j;
}
while (head<=tail&&q[head]<l[i]) ++head;
f[i]=(head<=tail)?(f[q[head]]+(i!=n+1)):-1;
}
return !printf("%d\n",f[n+1]);
}
洛谷 3085 阴和阳 Yin and Yang
题目
给出一棵 n n n个点的树,每条边为黑色或白色。问满足以下条件的路径条数:路径上存在一个不是端点的点,使得两端点到该点的路径上两种颜色的边数都相等。
分析
把黑色看为1,把白色看为-1,那么显然路径和为0,可以列出一个dp方程
f
/
g
[
n
o
w
]
[
0
/
1
]
f/g[now][0/1]
f/g[now][0/1]表示以当前节点为重心,路径和为
n
o
w
now
now,且以前/现在(g/f)遍历过,找不到/找得到端点的方案
dp方程显然,点分治求解,时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
详见代码
代码
#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
const int N=100011; bool v[N]; struct node{int y,w,next;}e[N<<1]; long long ans;
int big[N],son[N],d[N<<1],mson,root,mxdep,n,m,ls[N],tot,k=1,f[N<<1][2],g[N<<1][2];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline void dfs(int x,int fa){
son[x]=1,big[x]=0;
for (rr int i=ls[x];i;i=e[i].next)
if (e[i].y!=fa&&!v[e[i].y]){
dfs(e[i].y,x);
son[x]+=son[e[i].y];
big[x]=max(big[x],son[e[i].y]);
}
big[x]=max(big[x],mson-son[x]);
if (big[x]<big[root]) root=x;
}
inline void getd(int x,int fa,int now,int dep){
mxdep=max(mxdep,dep);
d[now]?++f[now][1]:++f[now][0];
++d[now];
for (rr int i=ls[x];i;i=e[i].next)
if (e[i].y!=fa&&!v[e[i].y])
getd(e[i].y,x,now+e[i].w,dep+1);
--d[now];
}
inline void calc(int x){
rr int mx=0; g[n][0]=1;
for (rr int i=ls[x];i;i=e[i].next)
if (!v[e[i].y]){
mxdep=1; getd(e[i].y,x,n+e[i].w,1);
mx=mx>mxdep?mx:mxdep;
ans+=f[n][0]*(g[n][0]-1);//它自己显然不能算
for (rr int j=-mxdep;j<=mxdep;++j)
ans+=f[n+j][1]*g[n-j][1]+f[n+j][0]*g[n-j][1]+f[n+j][1]*g[n-j][0];//至少要又一个找到中转点
for (rr int j=n-mxdep;j<=n+mxdep;++j)
g[j][0]+=f[j][0],g[j][1]+=f[j][1],f[j][0]=f[j][1]=0;
}
for (rr int i=n-mx;i<=n+mx;++i)
g[i][0]=g[i][1]=0;
}
inline void dp(int x){
v[x]=1,calc(x);
for (rr int i=ls[x];i;i=e[i].next)
if (!v[e[i].y]){
big[0]=mson=son[e[i].y];
dfs(e[i].y,root=0),dp(root);
}
}
signed main(){
n=iut();
for (rr int i=1;i<n;++i){
rr int x=iut(),y=iut(),w=(iut()<<1)-1;
e[++k]=(node){y,w,ls[x]},ls[x]=k;
e[++k]=(node){x,w,ls[y]},ls[y]=k;
}
big[0]=mson=n,dfs(1,0),dp(root);
return !printf("%lld",ans);
}
洛谷 3086 数字8 Figure Eight
题目
问在一个方阵中有多少个数字8,结果输出8上面所组成矩形的面积*8下面所组成的矩形的面积的总和
分析
原理很简单,设 d p [ k ] [ i ] [ j ] dp[k][i][j] dp[k][i][j]表示第 k k k行为矩形底部,以 i , j i,j i,j为左右边界所能组成的最大矩形的面积,先用 O ( n 3 ) O(n^3) O(n3)求出,再往两边延伸,那么最后以同样的方式暴力,时间复杂度 O ( n 3 ) O(n^3) O(n3)
代码
#include <cstdio>
#define rr register
using namespace std;
const int N=301; bool s[N][N];
int sum[N][N],dp[N][N][N],n,ans;
inline signed max(int a,int b){return a>b?a:b;}
signed main(){
scanf("%d",&n);
for (rr int i=1;i<=n;++i)
for (rr int j=1;j<=n;++j){
rr char c=getchar();
while (c!='.'&&c!='*') c=getchar();
sum[i][j]=sum[i][j-1]+(s[i][j]=(c=='*'));
}
for (rr int i=1;i<n-1;++i)
for (rr int j=i+2,p=0;j<=n;++j,p=0)
for (rr int k=1;k<=n;++k){
if (s[k][i]||s[k][j]) p=0;
if (sum[k][i-1]==sum[k][j])
!p?p=k:dp[k][i][j]=(k-p-1)*(j-i-1);
}
for (rr int k=1;k<=n;++k){
for (rr int j=n;j>3;--j)
for (rr int i=j-3;~i;--i)
if (sum[k][i-1]==sum[k][j])
dp[k][i][j]=max(dp[k][i][j],dp[k][i+1][j]);
for (rr int i=1;i<n-1;++i)
for (rr int j=i+3;j<=n;++j)
if (sum[k][i-1]==sum[k][j])
dp[k][i][j]=max(dp[k][i][j],dp[k][i][j-1]);
}
for (rr int i=1;i<n-1;++i)
for (rr int j=i+2,p=0;j<=n;++j,p=0)
for (rr int k=n;k;--k){
if (s[k][i]||s[k][j]) p=0;
if (sum[k][i-1]==sum[k][j])
!p?p=k:ans=max(ans,dp[k][i][j]*(p-k-1)*(j-i-1));
}
if (!ans) printf("%d",-1);
else printf("%d",ans);
return 0;
}