搜索
因为noip寄了,所以非常伤心,准备从2023开始加油!刷题!
今天是洛谷P1267
首先,枚举根节点,下一次选的点的值在1~4nn中,每选一个点,在该子树中的选点范围就会缩小,此时我们考虑用搜索,但它应该过不了,再考虑dp,f[i][j][k]表示以i为子树的根节点,以[j,k]为子树的选数范围,但ijk=(4nn)^3,依然过不了,但是对于一个以i根节点的子树来说,它选数范围的左端点或右端点一定是i的父结点的值,且每一个结点的父节点的可能性只有三个,因此记搜加map可过。
代码:
#include<iostream>
#include<map>
#include<cmath>
#define int long long
using namespace std;
int n;
struct node{
int to;
int nxt;
}edge[10010];
int head[4010],tot;
void addedge(int u,int v){
edge[++tot].to=v;
edge[tot].nxt=head[u];
head[u]=tot;
}
int dian[4010];
map<pair<int,pair<int,int> >,int> mp;
int f[5][4][20];
int dfs(int u,int l,int r){
if(mp[make_pair(u,make_pair(l,r))]!=0)return mp[make_pair(u,make_pair(l,r))];
int zuo=0;
int you=0;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(dian[v]>=l&&dian[v]<dian[u]){
zuo=max(zuo,dfs(v,l,dian[u]-1));
}
if(dian[v]>dian[u]&&dian[v]<=r){
you=max(you,dfs(v,dian[u]+1,r));
}
}
return mp[make_pair(u,make_pair(l,r))]=1+zuo+you;
}
signed main(){
cin>>n;
for(int i=1;i<=4;i++){
for(int j=1;j<=n*n;j++){
cin>>dian[(i-1)*n*n+j];
}
for(int j=1;j<=n*n;j++){
if((int)(sqrt(j))*(int)(sqrt(j))!=j){
addedge((i-1)*n*n+j,(i-1)*n*n+j+1);
}
if((int)(sqrt(j-1))*(int)(sqrt(j-1))!=j-1){
addedge((i-1)*n*n+j,(i-1)*n*n+j-1);
}
int heng=sqrt(j-1)+1;
if((heng+j)%2==0){
if(heng<n){
addedge((i-1)*n*n+j,(i-1)*n*n+j+2*heng);
}
}
else{
if(heng>1){
addedge((i-1)*n*n+j,(i-1)*n*n+j-2*heng+2);
}
}
}
for(int j=0;j<=n;j++){
f[i][2][j]=(i-1)*n*n+j*j;
}
for(int j=1;j<=n;j++){
f[i][1][j]=f[i][2][j-1]+1;
}
for(int j=1,k=f[i][1][n];j<=n;j++,k+=2){
f[i][3][j]=k;
}
}
for(int i=1;i<=n;i++){
addedge(f[1][2][i],f[2][1][i]);
addedge(f[2][1][i],f[1][2][i]);
addedge(f[1][1][i],f[3][2][i]);
addedge(f[3][2][i],f[1][1][i]);
addedge(f[3][1][i],f[2][2][i]);
addedge(f[2][2][i],f[3][1][i]);
addedge(f[2][3][i],f[4][2][i]);
addedge(f[4][2][i],f[2][3][i]);
}
for(int i=1,j=n;i<=n;i++,j--){
addedge(f[1][3][i],f[4][1][j]);
addedge(f[4][1][j],f[1][3][i]);
addedge(f[3][3][i],f[4][3][j]);
addedge(f[4][3][j],f[3][3][i]);
}
int ans=0;
for(int i=1;i<=4*n*n;i++){
ans=max(ans,dfs(i,1,4*n*n));
}
cout<<ans;
return 0;
}
我对搜索和dp有一些小小的想法,搜索复杂度爆炸是一个状态询问多次,dp复杂度爆炸是询问许多无用状态,个人认为记搜加map可以解决,当然,dp经过一定处理也可以完成。除此以外,dp的状态查询大部分都是有一定顺序的,若是排不出顺序的,可以用搜索解决。
学习折半搜索。
洛谷P4799
1.直接搜,比较简单的搜索,但只能过前两个子任务。
#include<iostream>
#include<algorithm>
using namespace std;
int n;
long long m;
long long a[50];
bool cmp(long long x,long long y){
return x<y;
}
long long dfs(int x,long long y){
if(x==n){
return 1;
}
long long ans=1;
for(int i=x+1;a[i]+y<=m&&i<=n;i++){
ans=ans+dfs(i,y+a[i]);
}
return ans;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1,cmp);
cout<<dfs(0,0);
return 0;
}
2.背包dp,可过1,3两个子任务。
#include<iostream>
using namespace std;
int n;
long long m;
long long a[1000010];
long long f[1000010];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
f[0]=1;
for(int i=1;i<=n;i++){
for(int j=m;j>=a[i];j--){
f[j]+=f[j-a[i]];
}
}
long long ans=0;
for(int j=0;j<=m;j++){
ans+=f[j];
}
cout<<ans;
return 0;
}
3但第二个子任务背包dp空间不可行,考虑day1的对dp的多余状态的优化,unordered_map,可过1,2,3三个子任务。
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
int n;
long long m;
long long a[50];
long long dui[10000010];
long long b[10000010];
int cnt2=0;
long long c[10000010];
int cnt=0;
bool cmp(long long x,long long y){
return x<y;
}
bool cnp(long long x,long long y){
return x>y;
}
unordered_map<long long ,long long> f;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1,cnp);
f[0]=1;
dui[++cnt]=0;
for(int i=1;i<=n;i++){
for(int j=cnt;j>=1;j--){
c[cnt-j+1]=dui[j];
}
int l=0,r=cnt;
while(l<r){
int mid=(l+r+1)/2;
if(a[i]+dui[mid]>m){
r=mid-1;
}
else{
l=mid;
}
}
cnt2=0;
for(int j=l;j>=1;j--){
if(f[dui[j]+a[i]]==0){
b[++cnt2]=dui[j]+a[i];
}
f[dui[j]+a[i]]+=f[dui[j]];
}
int cnt1=cnt;
int j=cnt1,k=cnt2;
cnt=0;
while(j>=1||k>=1){
if(j<1){
dui[++cnt]=b[k];
k--;
continue;
}
if(k<1){
dui[++cnt]=c[j];
j--;
continue;
}
if(c[j]<b[k]){
dui[++cnt]=c[j];
j--;
}
else{
dui[++cnt]=b[k];
k--;
}
}
}
long long ans=0;
for(int i=1;i<=cnt;i++){
ans+=f[dui[i]];
}
cout<<ans;
return 0;
}
4.考虑我们只需要知道总方案数是多少,并不需要知道各个状态的方案数是多少,第四个子任务n<=40,将它折半,就是两个第二个子任务,每个第二个子任务的状态数最多是2^n个,这样对两个第二个子任务分别按状态大小排序,前缀和加双指针,在第二个子任务中用map记录每个状态的方案数。
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
unsigned int n;
unsigned long long m;
unsigned long long a[50];
unordered_map<unsigned long long,unsigned long long>mp1;
unordered_map<unsigned long long,unsigned long long>mp2;
unsigned long long dui1[1100010];
unsigned int cnt1=0;
unsigned long long dui2[1100010];
unsigned int cnt2=0;
inline void dfs1(unsigned int x,unsigned long long y){
if(x==n){
return ;
}
for(int i=x+1;a[i]+y<=m&&i<=n;i++){
if(mp1[y+a[i]]==0){
dui1[++cnt1]=y+a[i];
}
mp1[y+a[i]]++;
if(y+a[i]<m)dfs1(i,y+a[i]);
}
}
inline void dfs2(unsigned int x,unsigned long long y){
if(x==n){
return ;
}
for(int i=x+1;a[i]+y<=m&&i<=n;i++){
if(mp2[y+a[i]]==0){
dui2[++cnt2]=y+a[i];
}
mp2[y+a[i]]++;
if(y+a[i]<m)dfs2(i,y+a[i]);
}
}
inline bool cmp(unsigned long long x,unsigned long long y){
return x<y;
}
unsigned long long qian[1100010];
int main(){
cin>>n>>m;
for(unsigned int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1,cmp);
dui2[++cnt2]=0;
mp2[0]=1;
dfs2(n/2,0);
n/=2;
dui1[++cnt1]=0;
mp1[0]=1;
dfs1(0,0);
sort(dui1+1,dui1+cnt1+1,cmp);
sort(dui2+1,dui2+cnt2+1,cmp);
for(unsigned int j=1;j<=cnt2;j++){
qian[j]=qian[j-1]+mp2[dui2[j]];
}
int j=cnt2;
unsigned long long ans=0;
for(unsigned int i=1;i<=cnt1&&j>0;i++){
while(j>0&&dui2[j]+dui1[i]>m){
j--;
}
if(j>0){
ans+=mp1[dui1[i]]*qian[j];
}
}
cout<<ans;
return 0;
}