2024.3.14 【深处暖春 ,才会怕冷。】
Thursday 二月初五
<BGM = “Night Crusing x Time to Pretend–ZsFlovexl”>
状压DP
T1 P2566 SCOI2009 围豆豆
先贴代码:
//2024.3.14
//by white_ice
#include<bits/stdc++.h>
using namespace std;
#define itn int
const int oo = 12;
int jntm(itn a,int b){return a>b?a:b;}
int n,m,d;
int out,f[oo][oo][1<<oo],da[oo],val[1<<oo];
struct nod{
int x,y,s;
nod(int a=0,int b=0,int c=0){
x=a,y=b,s=c;
}
}b[oo*oo];
char c[oo][oo];
int xx[4]={0,-1,0,1},yy[4]={-1,0,1,0},ax[oo],ay[oo];
int ans=-0x3f3f3f3f;
int vis[oo][oo][1<<oo];
int solve(int mx,int my,int nx,int ny,int ms){
int ns=ms;
for(int i=1;i<=d;i++){
if(((mx==ax[i] && nx<ax[i]) || (mx<ax[i] && nx==ax[i])) && ny>ay[i]){
ns^=(1<<(i-1));
}
}
return ns;
}
void spfa(int x,int y){
queue<nod> q;
q.push(nod(x,y,0));
memset(f,0x3f,sizeof(f));
f[x][y][0]=0;
while(!q.empty()){
nod p=q.front();
q.pop();
int mx=p.x,my=p.y,ms=p.s;
vis[mx][my][ms]=0;
for(int i=0;i<4;i++){
int nx=mx+xx[i],ny=my+yy[i];
if(nx<1||ny<1||nx>n||ny>m||(c[nx][ny]>='1'&&c[nx][ny]<='9')||c[nx][ny]=='#')
continue;
int ns=ms;
if(i&1) ns=solve(mx,my,nx,ny,ms);
if(f[mx][my][ms]<f[nx][ny][ns]){
f[nx][ny][ns]=f[mx][my][ms]+1;
if(vis[nx][ny][ns]==0){
vis[nx][ny][ns]=1;
q.push(nod(nx,ny,ns));
}
}
}
}
for(int i=0;i<out;i++)
ans=jntm(ans,val[i]-f[x][y][i]);
}
int main(){
scanf("%d%d%d",&n,&m,&d);
for(int i=1;i<=d;i++)
scanf("%d",&da[i]);
out=1<<d;
for(int i=0;i<out;i++)
for(int j=1;j<=d;j++)
if(i&(1<<(j-1)))
val[i]+=da[j];
for(int i=1;i<=n;i++)
scanf("%s",c[i]+1);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(c[i][j]>'0' && c[i][j]<='9'){
int now=c[i][j]-'0';
ax[now]=i;
ay[now]=j;
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(c[i][j]=='0'){
spfa(i,j);
}
}
}
printf("%d\n",ans);
return 0;
}
这个题其实很重要的一点就是射线定理
从一个点出发,向右发射一条射线,通过射线与路径轮廓线的交点个数(竖直方向移动的交点)可以判断是否被围在封闭图形中,射线定理,即交点数为奇数则在该封闭图形中。
直接遍历所有豆豆判断相交就可以
最终的状态转移方程为:ans=max(ans,val[i]-f[ii][jj][i]) val[i]是我们预处理出来的状态为i时豆子的总价值
T2 P2150 NOI2015 寿司晚宴
//2024.3.14
//by white_ice
#include<bits/stdc++.h>
using namespace std;
#define itn int
const int oo = 1e9+9;
const int op = 302;
int p[10] = {0,2,3,5,7,11,13,17,19,0};
int n,mod;
struct nod {
itn val,big,s;
void init(){
itn tmp = val;big = -1;
for (itn i=1;i<=8;i++){
if (tmp%p[i])
continue;
s|=(1<<i-1);
while(tmp%p[i]==0)
tmp/=p[i];
}
if (tmp!=1)
big = tmp;
}
}st[502];
bool cmp(nod a,nod b){return a.big<b.big;}
itn cou(itn l,int r){l+=r;return l>=mod?l-mod:l;}
itn f[op][op],fp[op][op],fq[op][op];
itn main(){
cin >> n >> mod;
for (int i=2;i<=n;i++)
st[i-1].val = i,st[i-1].init();
sort(st+1,st+n,cmp);
f[0][0] = 1;
for (itn i=1;i<n;i++){
if(i==1||st[i].big!=st[i-1].big||st[i].big==-1){
memcpy(fp,f,sizeof(fp));
memcpy(fq,f,sizeof(fq));
}
for(itn j=255;j>=0;j--){
for(itn k=255;k>=0;k--){
if(j&k)
continue;
if((st[i].s&j)==0)
fq[j][k|st[i].s]=cou(fq[j][k|st[i].s],fq[j][k]);
if((st[i].s&k)==0)
fp[j|st[i].s][k]=cou(fp[j|st[i].s][k],fp[j][k]);
}
}
if(i==n-1||st[i].big!=st[i+1].big||st[i].big==-1){
for(itn j=0;j<=255;j++){
for(itn k=0;k<=255;k++){
if(j&k)
continue;
f[j][k]=cou(fp[j][k],cou(fq[j][k],mod-f[j][k]));
}
}
}
}
long long out = 0;
for (itn j=0;j<=255;j++)
for (int k=0;k<=255;k++)
if ((j&k)==0&&f[j][k])
out = cou(out,f[j][k]);
cout << out;
return 0;
}
对于这个题,不难想到用所有素数将500以内的数直接解开,因为500并不大。
用因子的存在表示数的状态,再轮流模拟两个人取数就可以了。
T4 P3604 美好的每一天
//2024.3.14
//by white_ice
#include<bits/stdc++.h>
using namespace std;
#define itn int
const int oo = 60004;
int n,m;
int base;
long long st[oo];
struct nod{
itn l,r,id;
bool operator <(nod i)const{
return l/base==i.l/base?r<i.r:l<i.l;
}
}sp[oo];
itn out[oo];
itn cnt[(1<<26)],ned;
void ins(itn x){
ned += cnt[st[x]];
cnt[st[x]]++;
for (int i=0;i<26;i++)
ned+=cnt[st[x]^(1<<i)];
return ;
}
void del(itn x){
cnt[st[x]]--;
ned -= cnt[st[x]];
for (itn i=0;i<26;i++)
ned-=cnt[st[x]^(1<<i)];
return;
}
int main(){
cin >> n >> m;
base = 3*sqrt(n);
for (itn i=1;i<=n;i++){
char c;
cin >> c;
st[i] = 1<<(c-'a');
st[i]^=st[i-1];
}
for (int i=1;i<=m;i++){
cin >> sp[i].l >> sp[i].r;
sp[i].id = i;
}
sort(sp+1,sp+m+1);
itn l = 1,r = 0;
for (itn i=1;i<=m;i++){
itn x = sp[i].l-1;
itn y = sp[i].r;
itn id = sp[i].id;
while (l<x)del(l++);
while (l>x)ins(--l);
while (r>y)del(r--);
while (r<y)ins(++r);
out[id] = ned;
}
for (itn i=1;i<=m;i++)
cout << out[i] << endl;
return 0;
}
对于一个回文串,其只可能是所有字母都是偶数个或者仅有一个字母是奇数个,所以异或这种操作就显得非常奇技淫巧好用。
将26个英文字母设为对应的唯一2的整数次方数,于是就实现了状态压缩。
再运用前缀(异或)和这种方式进行维护就可以快速求解了。
求解部分是比较标准的莫队算法。
(其实本题不是DP,但是我真的很喜欢状态压缩和暴力数据结构。。。)