题目还是挺爽的。
P2327 [SCOI2005]扫雷
题目分析
我们设\(a[i]\)为第\(i\)行的数字,显然如果满足\(a[1]=3\vee a[n]=3\)时,方案数为\(0\)呐等于\(0\)。
所以接下来我们考虑搜索,设\(v[i]\)(\(bool\)类型)为第\(i\)行是否放了地雷(\(1\)表示放置,\(0\)表示没有放置)。
对于\(a[i]=3\)的情况,只需要将\(v[i-1],v[i]\)和\(v[i+1]\)赋为\(1\)即可。
接下来考虑\(a[i]=1\)和\(a[i]=2\)。
显然,当\(i-1\)至\(i+1\)范围中雷的个数\(sum=a[i]\)时,直接往后搜索即可,如果不等于,就要放置地雷来满足\(sum=a[i]\),同时需要考虑当前放置的棋子对之前放置的棋子产生的影响,所以在搜索中笔者记录了前两个格子分别在\(i-1\)至\(i+1\)内地雷个数,如果当前放置地雷位置导致了之前格子所包含的地雷数过多,或者此位置已经放置了地雷,则重新枚举位置。
点击查看代码
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ll long long
#define R register
using namespace std;
int n;
int a[10005];
bool v[10005];
ll ans;
inline ll re(){
R ll k=0,f=1ll;
R char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')
f=-1ll;
ch=getchar();
}
while(isdigit(ch)){
k=k*10ll+(ch^48ll);
ch=getchar();
}
return 1ll*k*f;
}
void wr(ll x){
if(x<0){
putchar('-');
x=~x+1;
}
if(x>9)
wr(x/10ll);
putchar(x%10+48ll);
}
void dfs(int x,int la,int s,int lla,int ss){ //记录前两个格子位置以及在格子范围内地雷个数
if(!x){
++ans;
return;
}
R int sum=0;
R int down=min(n,x+1),up=max(1,x-1);
for(R int i=down;i>=up;--i)
if(v[i])
++sum;
if(sum>a[x])
return;
if(sum==a[x]){
dfs(x-1,x,sum,la,s);
return;
}
for(R int i=down;i>=up;--i){
if(v[i])
continue;
if(a[x]==2){
if(i==x+1){
if((s+1!=a[la]||ss+1!=a[lla])&&la<=n&&lla<=n) //是否对之后状态产生影响
continue;
v[i]=1,++sum;
if(sum==1){
for(R int j=i-1;j>=up;--j){
if(j==x){
if(s+1!=a[la])
continue;
v[j]=1;
++sum;
dfs(x-1,x,sum,la,s);
--sum;
v[j]=0;
}
}
v[i]=0,--sum;
}
else if(i==x){
if(s+1!=a[la]&&la<=n) //是否对之后状态产生影响
continue;
v[i]=1,++sum;
if(sum==1){
v[i-1]=1,++sum;
dfs(x-1,x,sum,la,s);
v[i-1]=0,--sum;
}
v[i]=0,--sum;
}
else if(i==x-1){
v[i]=1,++sum;
if(sum!=a[x]) continue;
dfs(x-1,x,sum,la,s);
v[i]=0,--sum;
}
}
else if(a[x]==1){
if(i==x+1){
if((s+1!=a[la]||ss+1!=a[lla])&&la<=n&&lla<=n)
continue;
v[i]=1,++sum;
dfs(x-1,x,sum,la,s);
v[i]=0,--sum;
}
else if(i==x){
if(s+1!=a[la]&&la<=n)
continue;
v[i]=1,++sum;
dfs(x-1,x,sum,la,s);
v[i]=0,--sum;
}
else if(i==x-1){
v[i]=1,++sum;
dfs(x-1,x,sum,la,s);
v[i]=0,--sum;
}
}
else
dfs(x-1,x,sum,la,s);
}
}
signed main(){
n=re();
for(R int i=1;i<=n;++i){
a[i]=re();
if(a[i]>=3){
a[i]=3;
if(a[1]==3||a[n]==3){
puts("0");
return 0;
}
v[i]=v[i-1]=v[i+1]=1;
}
}
dfs(n,n+1,0,n+2,0);
wr(ans);
return 0;
}
P1896 [SCOI2005] 互不侵犯
题目分析
经典状压dp
令\(f[i][j][l]\)为前\(i\)行放置了\(l\)个国王,第\(i\)行状态为\(j\)的方案数
所以\(f[i][j][l]+=f[i-1][k][l-king[k]]\),其中\(k\)为第\(i-1\)行的状态,\(king[k]\)为状态\(k\)的国王个数
计算是否冲突有:
-
\(sit[j]\&sit[k]\)
-
\((sit[j]<<1)\&sit[k]\)
-
\(sit[j]\&(sit[k]<<1)\)
于是完事儿
点击查看代码
#include <cmath>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,m,cnt,ans;
ll sit[2010],king[2010];
ll f[15][2010][95];
inline ll re(){
ll k=0,f=1ll;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')
f=-1ll;
ch=getchar();
}
while(isdigit(ch)){
k=k*10ll+(ch^48ll);
ch=getchar();
}
return 1ll*k*f;
}
void wr(ll x){
if(x<0){
putchar('-');
x=~x+1;
}
if(x>9)
wr(x/10ll);
putchar(x%10+48ll);
}
inline void init_(int one,int kin,int now){
if(now>=n){
sit[++cnt]=one;
king[cnt]=kin;
return;
}
init_(one,kin,now+1);
init_(one+(1<<now),kin+1,now+2);
}
signed main(){
n=re(),m=re();
init_(0,0,0);
for(int i=1;i<=cnt;++i)
f[1][i][king[i]]=1;
for(int i=2;i<=n;++i){
for(int j=1;j<=cnt;++j){
for(int k=1;k<=cnt;++k){
if(sit[j]&sit[k]||(sit[j]<<1)&sit[k]||sit[j]&(sit[k]<<1))
continue;
for(int l=m;l>=king[k];--l)
f[i][j][l]+=f[i-1][k][l-king[j]];
}
}
}
for(int i=1;i<=cnt;++i)
ans+=f[n][i][m];
wr(ans);
return 0;
}
P2330 [SCOI2005]繁忙的都市
超级大水题之sort中把E+1+m写成E+1+n丢掉100pts
题目分析
显然就是一个求出最小生成树中的最大权值,由于按照权值排序,所以当符合条件的边个数达到\(n-1\)时输出第\(n-1\)条符合条件的边的权值即可
点击查看代码
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll ans;
int n,m;
int fa[305];
inline ll re(){
ll k=0,f=1ll;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')
f=-1ll;
ch=getchar();
}
while(isdigit(ch)){
k=k*10ll+(ch^48ll);
ch=getchar();
}
return 1ll*k*f;
}
void wr(ll x){
if(x<0){
putchar('-');
x=~x+1;
}
if(x>9)
wr(x/10ll);
putchar(x%10+48ll);
}
struct Edge{
int u,v,w;
}E[100005];
inline bool cmp(Edge a,Edge b){
return a.w<b.w;
}
int find_(int x){
return (x==fa[x])?x:(fa[x]=find_(fa[x]));
}
signed main(){
n=re(),m=re();
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1,u,v,w;i<=m;++i){
u=re(),v=re(),w=re();
E[i].u=u,E[i].v=v,E[i].w=w;
}
sort(E+1,E+1+m,cmp);
int cnt=0;
for(int i=1;i<=m;++i){
int u=E[i].u,v=E[i].v;
int x=find_(u);
int y=find_(v);
if(x!=y){
fa[x]=y;
int w=E[i].w;
ans=w;
++cnt;
if(cnt==n-1) break;
}
}
wr(n-1),putchar(' '),wr(ans);
return 0;
}
P2331 [SCOI2005]最大子矩阵
爽题
题目分析
当\(m=1\)时,就是一个最大子段和问题,所以设\(f_1[i][j]\)为在\(i\)位置已经有\(j\)个矩阵时的最大和
那我们的状态就有两种:
-
不选择一段区间
\[f_1[i][j]=f_1[i-1][j] \] -
选择一段区间
其中\(sum[i]\)表示以\(i\)点结尾的前缀和,目标状态:\(f_1[n][K]\)
当\(m=2\)时,情况有些复杂,不妨将其看作是两个\(m=1\)的段拼凑在一起,所以我们的状态设置就可以有\(f_2[i][j][k]\)表示在第1列以\(i\)结尾,第2列以\(j\)结尾,已经选了\(k\)个矩阵的最大和,所以我们设\(sum_1[i]\)为第一列中\(i\)的前缀和,\(sum_2[i]\)表示第二列中\(i\)的前缀和
下面,我们一一来分析状态:
- 既不选择\(i\)点的数,也不选择\(j\)点的数
- 仅选择第1列的一段区间
- 仅选择第2列的一段区间
- 二者都选,当且仅当\(i=j\)
目标状态:\(f[n][n][K]\)
点击查看代码
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,m,K;
int sum1[105],sum2[105];
int f1[105][15],f2[105][105][15];
inline ll re(){
ll k=0,f=1ll;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')
f=-1ll;
ch=getchar();
}
while(isdigit(ch)){
k=k*10ll+(ch^48ll);
ch=getchar();
}
return 1ll*k*f;
}
void wr(ll x){
if(x<0){
putchar('-');
x=~x+1;
}
if(x>9)
wr(x/10ll);
putchar(x%10+48ll);
}
signed main(){
n=re(),m=re(),K=re();
if(m&1){
for(int i=1;i<=n;++i){
int x=re();
sum1[i]=sum1[i-1]+x;
}
for(int k=1;k<=K;++k){
for(int i=1;i<=n;++i){
f1[i][k]=f1[i-1][k];
for(int j=0;j<i;++j)
f1[i][k]=max(f1[i][k],f1[j][k-1]+sum1[i]-sum1[j]);
}
}
wr(f1[n][K]);
}
else{
for(int i=1;i<=n;++i){
int x=re(),y=re();
sum1[i]=sum1[i-1]+x,sum2[i]=sum2[i-1]+y;
}
for(int k=1;k<=K;++k){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
f2[i][j][k]=max(f2[i-1][j][k],f2[i][j-1][k]);
for(int l=0;l<i;++l)
f2[i][j][k]=max(f2[i][j][k],f2[l][j][k-1]+sum1[i]-sum1[l]);
for(int l=0;l<j;++l)
f2[i][j][k]=max(f2[i][j][k],f2[i][l][k-1]+sum2[j]-sum2[l]);
if(i==j)
for(int l=0;l<i;++l)
f2[i][j][k]=max(f2[i][j][k],f2[l][l][k-1]+sum1[i]+sum2[j]-sum1[l]-sum2[l]);
}
}
}
wr(f2[n][n][K]);
}
return 0;
}