@…
背包dp
A. Cut Ribbon
链接:传送门
还是三种色带的话就是相当于三层dp嘛 所以这样子就okk了
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout << #x << " == " << x << endl;
const ll int MAX_N = 1e5 + 10;
int dp[MAX_N] = {0};
int main()
{
int n, b[4];
scanf("%d %d %d %d", &n, &b[1], &b[2], &b[3]);
for (int i = 1; i <= 3; i++)
{
for (int j = 0; j <= n; j++)
{
if ((j - b[i] == 0) || (j >= b[i] && dp[j - b[i]]))
{
dp[j] = max(dp[j], dp[j - b[i]] + 1);
}
}
}
printf("%d\n", dp[n]);
///system("pause");
}
樱花(多重背包,二进制拆分)
链接:传送门
#include <bits/stdc++.h>
using namespace std;
#define bug(x) cout << #x << " == " << x << endl;
typedef pair<int, int> ppp;
const int INF = 0x3f3f3f3f; //要比hp大
const int MAX_N = 1e4 + 10;
struct node
{
int t;
int c;
int p;
} tt[MAX_N];
#define ll long long
int dp[20005]= { 0 };
int cost[1000005] = { 0 };
int v[1000005] = { 0 };
int mod = 2;
int tot = 0;
int n;
inline void pre()
{
for(int i=1;i<=n;i++)
{
int t = 1;
while(tt[i].p)
{
tot++;
cost[tot] = t * tt[i].t;
v[tot] = t * tt[i].c;
tt[i].p -= t;
t *= 2;
if(tt[i].p<t)
{
tot++;
cost[tot] = tt[i].t * tt[i].p;利用这个二进制拆分
v[tot] = tt[i].c * tt[i].p;
break;
}
}
}
}
int main()
{
int h1,m1,h2,m2;
scanf("%d:%d %d:%d",&h1,&m1,&h2,&m2);
int T=60*(h2-h1)+(m2-m1);
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%d %d %d", &tt[i].t, &tt[i].c, &tt[i].p);
if(tt[i].p==0)
{
tt[i].p=T/tt[i].t;
}
}
pre();
for(int i=1;i<=tot;i++)
{
for(int j=T;j>=cost[i];j--)
{
dp[j]=max(dp[j],dp[ j-cost[i] ] + v[i]);
}
}
printf("%d\n",dp[T]);
}
普通dp
线性dp
P1233 木棍加工 (dilworth定理)
链接:门
用贪心排序后,在n个数中,求不下降子序列最少个数。
根据dilworth定理,不下降子序列最小个数等于最大上升子序列的长度。
不上升子序列的最小个数大于最大下降子序列的长度。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=2e5+5;
ll mod = 1e6+7;
struct node
{
int l,w;
bool operator<(node b)
{
if(l!=b.l)
return l>b.l;
return w>=b.w;
}
}a[maxn];
int dp[maxn] = { 0 };
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&a[i].l,&a[i].w);
}
sort(a+1,a+1+n);
int ans = 0;
for(int i=1;i<=n;i++)
{
for(int j=i-1;j>=1;j--)
{
if(a[i].w>a[j].w)
{
dp[i]=max(dp[i],dp[j]+1);///常见dp板子
}
}
ans=max(ans,dp[i]);
}
printf("%d\n",ans+1);
}
Dynasty Puzzles
链接:门
今天又是读不懂题意的一天QwQ
题目大意:要是A字符串的首字母是B字符串的尾字母,B字符串的首字母是A字符串的尾字母两个字符串可以相连,求首尾最长字符串。所以我们可以dp写,dp[i][j]代表开头为字母i+‘a’,结尾为j+'a’的字符串的最长长度每次读入一个字符串
开头为beginn,结尾为endd更新每个dp[i][endd]还有dp[beginn][endd]就好,因为最后要得到的结果是dp[i][i]首尾字母相同的最长字符串,所以按着往后添加字符串就好,最终结果和往前添加相同,而且不会重复计算
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
#define speed(x) ios::sync_with_stdio(false), cin.tie(x), cout.tie(x)
#define bug(x) cout << #x << " == " << x << '\n'
const ll MAX_N =1e6+10;
int dp[30][30];
int n;
int main()
{
int n;
char a[30];
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",a);
int len=strlen(a);
int beginn=a[0]-'a';
int endd=a[len-1]-'a';
for(int i=0;i<26;i++)
{
if(dp[i][beginn])
dp[i][endd]=max( dp[i][beginn]+len,dp[i][endd] );
}
}
int ans=0;
for(int i=0;i<26;i++)
{
ans=max( ans, dp[i][i] );
}
printf("%d\n",ans);
}
dp(cf里的不错好题)
CF933A A Twisty Movement
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn = 5e3+5;
ll mod = 1e6+7;
int dp[4] = { 0 };
///dp[1] 代表了 1,1.....
///dp[2] 代表了 1,1.....1,2....的情况
/// dp[3] 代表了 1,1.....1,(2...,1,...)翻转
/// dp[4] 代表了 1....,(2....,1....,)2....
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
if(x == 1)
{
dp[1] = dp[1] + 1;
dp[3] = max(dp[2],dp[3]) + 1;
}
else if(x==2)
{
dp[2] = max(dp[2],dp[1]) + 1;
dp[4] = max(dp[3],dp[4]) + 1;
}
}
printf("%d\n",max( max( dp[1],dp[2] ),max( dp[3],dp[4] ) ) );
}
递推
Unidirectional TSP(逆推)
链接: 我家大门常打开.
记录路径,所以要逆推,and特判下n==1的情况就ok了
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define ll long long
#define bug(x) cout << #x << " == " << x << '\n'
const ll int MAX_N=2e2+5;
ll dp[MAX_N][MAX_N]= { 0 };
int path[MAX_N][MAX_N]= { 0 };
int a[MAX_N][MAX_N]= { 0 };
int main()
{
int n,m;
while(scanf("%d %d",&m,&n)!=EOF)///m行n列
{
if(n==1)
{
int b[MAX_N]={0};
ll minn=1e8;
int index=0;
for(int i=1;i<=m;i++)
{
scanf("%d",&b[i]);
if(minn>b[i])
{
minn=b[i];
index=i;
}
}
printf("%d\n",index);
printf("%d\n",minn);
}
else
{
for(int i=1; i<=m; i++)
{
for(int j=1; j<=n; j++)
{
scanf("%d", &a[i][j]);
}
dp[i][n]=a[i][n];///逆推时间到
}
int maxx=1e8;
int qd=0;
for(int j=n-1; j>=1; j--)
{
for(int i=1; i<=m; i++)
{
dp[i][j]=1e8;
int way[7]= {i-1,i,i+1}; ///三种办法走
if(i-1==0)
way[0]=m;
if(i==m)
way[2]=1;
sort(way,way+3);///字典序嘛
for(int k=0; k<3; k++)
{
int used=dp[way[k]][j+1]+a[i][j];
if(used<dp[i][j])
{
dp[i][j]=used;
path[i][j]=way[k];
}
}
if(j==1)
{
if(maxx>dp[i][1])
{
maxx=dp[i][1];
qd=i;
}
}
}
}
printf("%d",qd);
int i=path[qd][1];
int j=1;
while(j<n)
{
printf(" %d",i);
j++;
i=path[i][j];
}
printf("\n%d\n",maxx);
}
}
}
A Spy in the Metro (逆推)
链接: lalalala.
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
#define bug(x) cout << #x << " == " << x << '\n'
const ll int MAX_N=2e2+5;
int n,T;
bool has_car[250][60][2]= {0};
int rr[60]= { 0 };
int l[60]= { 0 };
ll dp[250][60] = { 0 };
int a[60]= { 0 };
int cas=0;
void solve()
{
cas++;
for(int i=1; i<n; i++)
dp[T][i]=1e8;///时间T时刻我应该在车站N才对 所以别的地方都是1e8
dp[T][n]=0;///在T时刻等待的时间为0
for(int i=T-1; i>=0; i--)
{
for(int j=1; j<=n; j++)
{
dp[i][j]=dp[i+1][j]+1;///下一秒在呆在这个车站的等待时间
if(j<n&&has_car[i][j][1]&&i+a[j]<=T)
{
dp[i][j]=min(dp[i][j],dp[ i + a[j] ][j+1]);
///下一秒上了向右的车子,去了下一站,所以车站的等待时间和i+a[j]时刻在j+1车站相同
}
if(j>1&&has_car[i][j][0]&&i+a[j-1]<=T)
{
dp[i][j]=min(dp[i][j],dp[ i + a[j-1] ][j-1]);
///下一秒上了向左的车......
}
}
}
if(dp[0][1]>=1e8)
printf("Case Number %d: impossible\n",cas);
else
printf("Case Number %d: %lld\n",cas,dp[0][1]);
}
int main()
{
while( scanf("%d",&n)!=EOF,n)
{
scanf("%d",&T);
memset(has_car,false,sizeof(has_car));
for(int i=1; i<n; i++)
{
scanf("%d",&a[i]);
}
a[n]=0;///注意初始化车站两边哦
a[0]=0;
int r;
scanf("%d",&r);///右发的车
for(int i=1; i<=r; i++)
{
scanf("%d",&rr[i]);
int sum = rr[i];
for(int j=0; j<n; j++)
{
sum+=a[j];
if(sum>T)
break;
has_car[sum][j+1][1]=1;///在sum时间下,j+1车站有向右开的车子
}
}
int lll;
scanf("%d",&lll);
for(int i=1; i<=lll; i++)
{
scanf("%d",&l[i]);
int sum = l[i];
for(int j=n; j>=1; j--)
{
sum+=a[j];
if(sum>T)
break;
has_car[sum][j][0]=1;
}
}
solve();
}
}
Color Length
链接: 阿巴阿巴.
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define speed(x) ios::sync_with_stdio(false), cin.tie(x), cout.tie(x)
#define bug(x) cout << #x << " == " << x << '\n';
using namespace std;
const ll int MAX_N = 5e3 + 5;
ll dp[MAX_N][MAX_N] = {0};
int tot[MAX_N][MAX_N] = {0}; ///有多少个元素出现还没有取完
string a,b;
int apos_begin[30] = {0};
int apos_endd[30] = {0};
int bpos_begin[30] = {0};
int bpos_endd[30] = {0};
inline int get(int i,int j)
{
int ans=0;
for(int k=0; k<26; k++)
{
bool flag1=0,flag2=0;
if(i>=apos_begin[k]||j>=bpos_begin[k])
flag1=1;
if(i<apos_endd[k]||j<bpos_endd[k])
flag2=1;
if(flag1&&flag2)
ans++;
}
return ans;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
cin>>a>>b;
int n=a.length();
int m=b.length();
a=' '+a;
b=' '+b;
fill(apos_begin,apos_begin+28,INF);
fill(apos_endd,apos_endd+28,-INF);
fill(bpos_begin,bpos_begin+28,INF);
fill(bpos_endd,bpos_endd+28,-INF);
for(int i=1; i<=n; i++)
{
apos_begin[a[i]-'A']=min(i,apos_begin[a[i]-'A']);
apos_endd[a[i]-'A']=i;
}
for(int i=1; i<=m; i++)
{
bpos_begin[b[i]-'A']=min(i,bpos_begin[b[i]-'A']);
bpos_endd[b[i]-'A']=i;
}
for(int i=0; i<=n; i++)
{
for(int j=0; j<=m; j++)
{
dp[i][j]=INF;
}
}
dp[0][0] = 0;
for(int i=1; i<=n; i++)
{
dp[i][0] = dp[i - 1][0] + get(i, 0);
}
for(int j=1; j<=m; j++)
dp[0][j] = dp[0][j - 1] + get(0, j);
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+get(i,j);
}
}
printf("%lld\n",dp[n][m]);
}
}
Tour
链接: 巴卡巴卡.
有点累,晚上补补
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define ll long long
#define bug(x) cout << #x << " == " << x << '\n'
const ll int MAX_N=2e2+5;
int n,T;
struct node
{
double x,y;
}a[MAX_N];
double dp[MAX_N][MAX_N];
double l[MAX_N][MAX_N];
inline double len(node a,node b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%lf %lf",&a[i].x,&a[i].y);
for(int j=1;j<=n;j++)
{
dp[i][j]=(double)1e8;
}
for(int j=1;j<i;j++)
{
l[i][j]=l[j][i]=len(a[i],a[j]);
}
}
dp[2][1]=l[2][1];
for(int j=1;j<n-1;j++)
{
for(int i=j+1;i<n;i++)
{
dp[i+1][j]=min(dp[i+1][j],dp[i][j]+l[i][i+1]);
dp[i+1][i]=min(dp[i+1][i],dp[i][j]+l[i+1][j]);
}
}
double maxx=1e8;
for(int i=1;i<n-1;i++)
{
double tot=dp[n-1][i]+l[n-1][n]+l[i][n];
maxx=min(maxx,tot);
}
printf("%.2lf\n",maxx);
}
}
区间dp
Cutting Sticks(区间dp)
题目大意:把一个棍子切n次,得到n+1段木棍,求最小的费用,每次花费为棍子原长感觉就是合并石头的逆推版。
所以把棍子拆成n+1份然后合并就可以啦,需要注意的是,
会重复计算一次总长度,所以结果要减去一次总长
(因为最开始的一次合并就是第i段和第i+1段的和,不需要加上a[i+1]-a[i]但是为了方便书写,直接都加上了,会重复多加一个长度)
链接:加油加油.
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
#define speed(x) ios::sync_with_stdio(false), cin.tie(x), cout.tie(x)
#define bug(x) cout << #x << " == " << x << '\n'
const ll MAX_N =1e6+10;
int a[MAX_N]= {0};
int dp[60][60]= {0};
int main()
{
int len;
while(scanf("%d",&len),len)
{
int n;
scanf("%d",&n);
a[n+1]=len;
for(int i=0; i<=n+1; i++)
{
for(int j=0; j<=n+1; j++)
{
dp[i][j]=INF;
}
}
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
dp[i-1][i]=a[i]-a[i-1];
}
dp[n][n+1]=len-a[n];
for(int cd=1; cd<=n+1; cd++)
{
for(int i=0; i+cd<=n+1; i++)
{
int j=i+cd;
for(int k=i+1; k<j; k++)
{
dp[i][j]=min((dp[i][k]+dp[k][j])+a[j]-a[i],dp[i][j]);
}
}
}
printf("The minimum cutting is %d.\n",dp[0][n+1]-len);
}
}
E. Array Shrinking(区间dp)
反思:又是一道区间dp的题目,可惜自己写的的时候只考虑了右边往左边合并的情况于是用了stack来写,但是可能会存在左边往右边合并才是最优的情况,应该把每一段区间的最优解计算出来,这样子才可以算出数组1~n的最优解
链接:可莉真可爱
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
#define speed(x) ios::sync_with_stdio(false), cin.tie(x), cout.tie(x)
#define bug(x) cout << #x << " == " << x << '\n'
const ll MAX_N =5e2+10;
int a[MAX_N] ={0};
int dp[MAX_N][MAX_N] = {0};///dp代表在区间i~j之间压缩的最小长度
int s[MAX_N][MAX_N] = {0};///s等于在区间i~j之间压缩出来的值
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
s[i][i]=a[i];
for(int j=i;j<=n;j++)
{
dp[i][j]=j-i+1;///一开始在区间i~j之间的长度
}
}
for(int l=1;l<=n;l++)///区间长度
{
for(int i=1;i+l<=n;i++)///区间起点
{
int j=i+l;
for(int k=i;k<j;k++)
{
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
if( dp[i][k]==1 && dp[k+1][j]==1 && s[i][k]==s[k+1][j] )///说明左边可以合并成为一个元素,右边同
且左边合并出来的元素等于右边
{
s[i][j] = s[i][k]+1;
dp[i][j] = 1;
}
}
}
}
printf("%d\n",dp[1][n]);
}
[SCOI2003]字符串折叠
链接:题意真的坑
一开始都没有看懂样例解释
NEERCYESYESYESNEERCYESYESYES
等价于:2( N E E R 3 ( Y E S ) )一共有14个字符(加数学和字母,括号)
可以看出来EE=2(E)但是反而没有不合并的时候优 所以都是要比较的
很容易想到区间dp将大问题转化为一个个小问题
!!!!循环节的判断是重点 代码如下:
#include <bits/stdc++.h>
using namespace std;
#define bug(x) cout << #x << " == " << x << endl;
typedef pair<int, int> ppp;
const int INF = 0x3f3f3f3f;
const int MAX_N = 1e2 + 10;
#define ll long long
string a;
bool check(int l, int r, int len) //截取的区间为[l,r],循环节为len
{
for (int i = l; i <= r; i++)
{
if (a[i] != a[(i - l) % len + l])
{
return false;
}
}
return true;
}
int num[120] = {0};
int dp[120][120] = {0};
signed main()
{
cin >> a;
int n = a.length();
a = " " + a;
for (int i = 1; i <= 9; i++)
num[i] = 1;
for (int i = 10; i <= 99; i++)
num[i] = 2;
num[100] = 3; ///三位数的字符串占位有三个,上同理
memset(dp, INF, sizeof(dp));
for (int i = 1; i <= n; i++)
{
dp[i][i] = 1; 预处理:区间[i,i]只会有一个字母
}
for (int cd = 2; cd <= n; cd++) ///区间dp
{
for (int l = 1; l + cd - 1 <= n; l++)
{
int r = l + cd - 1;
for (int k = l; k < r; k++)
{
dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]);
///左右两个区间的简单合并
int len = k - l + 1; ///循环节大小
if (cd % len == 0) ///区间长度刚刚好可以分成d个循环节份
{
if (check(l, r, len))
{
dp[l][r] = min(dp[l][r], dp[l][k] + 2 + num[cd / len]);
///有两个括号所以加2,然后添加的数字占num[cd/len];
///dp[l][k]刚好代表了循环节的最短长度
}
}
}
}
}
printf("%d\n", dp[1][n]);
system("pause");
}
树状DP
Another Crisis
链接:好想打游戏
题目大意:有一个老板n个员工组成的树状结构,每一个员工,工人都只有唯一的直属上司,且工人没有下属,每一个上司只有不少于T%的直属下属交了请愿书,他才会上交给他的直属上司,现在要求最少多少工人交了请愿书,老板才会看见。
INPOT:
n个员工,输入百分比T
然后输入每一个员工的直属上司
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
#define speed(x) ios::sync_with_stdio(false), cin.tie(x), cout.tie(x)
#define bug(x) cout << #x << " == " << x << '\n'
const ll MAX_N =1e5+10;
vector<int> sonn[MAX_N];
int n,T;
int dp(int u)///头结点
{
if(sonn[u].empty())
return 1;///这是一个没有下属的最底层员工
int k=sonn[u].size();
vector<int> d;
for(int i=0; i<k; i++)
{
d.push_back( dp( sonn[u][i] ) );
}
sort( d.begin(), d.end() ); ///排序取最小的需要员工数目
int need=(k*T-1)/100+1;///u员工会上报给上级所需要的最低直接下属数目
int ans=0;
for(int i=0; i<need; i++)
{
ans+=d[i];///u员工会上报给上级所需要的最低员工数目
}
return ans;
}
int main()
{
while(scanf("%d %d",&n,&T)!=EOF,n,T)
{
int b;for(int i=1; i<=n; i++)
{
scanf("%d",&b);
sonn[b].push_back(i);
}
printf("%d\n", dp(0) );
for(int i=0; i<=n; i++)
{
sonn[i].clear();
}
}
}
[CTSC1997]选课
去洛谷搜吧,懒得放链接了
设一个虚假的头节点遍历所有的办法就很nice!!
#include <bits/stdc++.h>
using namespace std;
#define bug(x) cout << #x << " == " << x << endl;
typedef pair<int, int> ppp;
const int INF = 0x3f3f3f3f;
const int MAX_N = 2e3 + 10;
#define ll long long
struct node
{
int u,v,next;
}e[MAX_N];
int head[MAX_N] = { 0 };
int tot=0;
inline void add_edge(int u,int v)
{
tot++;
e[tot].u=u;
e[tot].v=v;
e[tot].next=head[u];
head[u]=tot;
}
int n,m;
int f[MAX_N][MAX_N] = { 0 };
void dp(int u)
{
for(int i=head[u]; i; i=e[i].next)
{
int v=e[i].v;
dp(v);
for(int j=m+1;j>=1;j--)///j代表选j门课,因为每一步都是从0开始的 所以是m+1
{
for(int k=0;k<j;k++)
{
f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
///u是选的第一门课,就是头结点,u;里面选的课是含有v的,所以可以以v为头结点再次拓展
}
}
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
int u,w;
scanf("%d %d",&u,&w);
f[i][1] = w;
add_edge(u,i);
}
dp(0);///每一个的没有直接选修课的都可以作为头结点,一开始的操作 将0和这些点点作为单项边连起来了
printf("%d\n",f[0][m+1]);
}
poj 2152 Fire
好难 不会 占个坑
Computer(求树的直径,求点的最大距离的好办法)
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define P pair <int,int>
using namespace std;
#define bug(x) cout<<#x<<"=="<<x<<endl;
const ll int maxn=1e5+10;
int head[maxn] = { 0 };
ll dis[maxn] = { 0 };
int tot = 0;
ll max_len = 0;
int s = 0;
struct node
{
int x;
int y;
ll w;
int next;
} way[maxn];
void add_edge(int u,int v,ll w)
{
tot++;
way[tot].x = u;
way[tot].y = v;
way[tot].w = w;
way[tot].next = head[u];
head[u] = tot;
}
void dfs(int u,int fu,ll len)
{
if(len>=max_len)
{
max_len = len;
s = u;
}
for(int i=head[u]; i; i=way[i].next)
{
int v=way[i].y;
if(v!=fu)
{
dfs(v,u,len+way[i].w);
dis[v]=max(dis[v],len+way[i].w);
}
}
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
memset( head,0,sizeof(head) );
memset(dis,0,sizeof(dis));
tot=0;
for(int i=2; i<=n; i++)
{
int v;
ll w;
scanf("%d %lld",&v,&w);
add_edge(i,v,w);
add_edge(v,i,w);
}
max_len=0;
s = 0;
dfs(1,0,0);
///此时求出来的s就是树的直径的一个端点
///因为如果1在树的直径上,遍历出来的s肯定是树得到直径的端点
///如果1不在树的直径上,最后遍历出来的也是直径的端点的,画图可以画出来
dfs(s,0,0);///树的直径的起点开始遍历
///只需要dfs两遍就可以求出树的直径啦
dfs(s,0,0);///树的直径的终点开始遍历
for(int i=1; i<=n; i++)
{
printf("%lld\n",dis[i]);
}
}
}
UVA1218 完美的服务 Perfect Services
上洛谷找题即可
几个状态真的好难想 还有怎么转移 难的QwQ只能多加练习了
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=1e4+10;
const ll int INF=1e5+10;
int n;
int tot=0;
vector<int> way[maxn];
ll dp[maxn][3] = { 0 };
///dp[j][0]为j为服务器,每个子结点可以是服务器也可以不是服务器
///dp[j][1]为j不是服务器,但是j的父亲是服务器,这意味着j的所有子结点都不是服务器
///题目要求 u 所连接的所有点中有且仅有一台服务器
///dp[j][2]u和u的父亲都不是服务器。这意味着u恰好有一个儿子是服务器。
void dfs(int u,int fu)
{
dp[u][0] = 1;
for(auto v:way[u])
{
if(v!=fu)
{
dfs(v,u);
dp[u][0] +=min(dp[v][0],dp[v][1]);
///该点为服务器,所以每个子结点可以是服务器也可以不是服务器
dp[u][1] += dp[v][2];///u的所有子结点v都不是服务器,而且自己也不是服务器
}
}
dp[u][2] = INF;
for(auto v:way[u])
{
if(v!=fu)
{
dp[u][2]=min(dp[u][2],dp[u][1]-dp[v][2]+dp[v][0]);
///选择v为服务器,所以是其他所有的子结点都是dp[vvv][2]的状态,只有v是dp[v][0]的状态
}
}
}
int main()
{
int u,v;
while(scanf("%d",&n)!=EOF)
{
tot = 0;
for(int i=1;i<=n;i++)
way[i].clear();
for(int i=1;i<n;i++)
{
scanf("%d %d",&u,&v);
way[u].push_back(v);
way[v].push_back(u);
dp[i][0] = 0;
dp[i][1] = 0;
dp[i][2] = 0;
}
dp[n][0] = 0;
dp[n][1] = 0;
dp[n][2] = 0;
dfs(1,0);
printf("%lld\n", min(dp[1][0],dp[1][2]) );
///根结点不存在父亲结点
int now;
scanf("%d",&now);
if(now==-1)break;
}
}
0-1tree(难题,难惹,要考虑很多状态)
链接:写了好久好久( ̄▽ ̄)
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define P pair <int,int>
using namespace std;
#define bug(x) cout<<#x<<"=="<<x<<endl;
int tot = 0;
const ll int maxn = 1e5+10;
int dp[maxn][4] = { 0 };
///dp[i][0]以i为起点,所有子路径全为0的个数
///dp[i][1]以i为起点,所有子路径先0后1的个数
///dp[i][2]以i为起点,所有子路径先1后0的个数
///dp[i][3]以i为起点,所有子路径都为1的个数
int head[maxn] = { 0 };
struct node
{
int x;
int y;
int next;
int w;
} way[maxn];
inline void add_edge(int u,int v,int w)
{
tot++;
way[tot].x = u;
way[tot].y = v;
way[tot].w = w;
way[tot].next = head[u];
head[u] = tot;
}
ll sum[4] = { 0 };
ll ans = 0;
void dfs(int x,int fx)
{
for(int i=head[x]; i; i=way[i].next)
{
int y = way[i].y;
int w = way[i].w;
if(y != fx)
{
dfs(y,x);
if(w==0)
{
sum[0] = dp[y][0] + 1;
sum[1] = dp[y][1] + dp[y][3];
sum[2] = sum[3] = 0;
}
else
{
sum[2] = dp[y][0] + dp[y][2];
sum[3] = dp[y][3] + 1;
sum[0] = sum[1] = 0;
}
ans += 2*sum[0]*dp[x][0];
ans += sum[0]*dp[x][3];
ans += sum[0]*dp[x][1];
ans += sum[1]*dp[x][0];
ans += sum[2]*dp[x][3];
ans += sum[3]*dp[x][0];
ans += sum[3]*dp[x][2];
ans += 2*sum[3]*dp[x][3];
for(int i=0; i<4; ++i)
dp[x][i]+=sum[i];
}
}
ans += (dp[x][0]*2+dp[x][1]+dp[x][2]+dp[x][3]*2);
}
int main( )
{
int n;
cin>>n;
for(int i=1; i<n; i++)
{
int u,v,w;
scanf("%d %d %d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
dfs(1,0);
cout<<ans<<endl;
}
Starship Troopers
#include <bits/stdc++.h>
using namespace std;
const int maxn=105;
int n,m,len;
int dp[maxn][maxn];//dp表示在i点放j人能得到的能量
int bug[maxn],p[maxn],vis[maxn],head[maxn];
struct node
{
int now,next;
} tree[maxn*2];
void add(int x,int y)
{
tree[len].now = y;
tree[len].next = head[x];
head[x] = len++;
}
void dfs(int root)
{
int cost,i,j,k,son;
vis[root] = 1;
cost = (bug[root]+19)/20;
for(i = cost; i<=m; i++)
dp[root][i] = p[root];
for(i = head[root]; i!=-1; i = tree[i].next)
{
son = tree[i].now;
if(!vis[son])
{
dfs(son);
for(j = m; j>=cost; j--)
{
for(k = 1; j+k<=m; k++)
{
if(dp[son][k])
dp[root][j+k] = max(dp[root][j+k],dp[root][j]+dp[son][k]);
}
}
}
}
}
int main()
{
int i,j,x,y;
while(~scanf("%d%d",&n,&m),n+m>0)
{
for(i = 1; i<=n; i++)
{
scanf("%d%d",&bug[i],&p[i]);
vis[i] = 0;
head[i] = -1;
}
len = 0;
for(i = 1; i<n; i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
if(!m)
{
printf("0\n");
continue;
}
memset(dp,0,sizeof(dp));
dfs(1);
printf("%d\n",dp[1][m]);
}
return 0;
}
优化dp
Making the Grade(优化dp,记录中间值)
①Making the Grade
链接: 加油加油加油!!!.
dp[i][j]=dp[i-1] [k] + min(a[i]-b[j]);(1<=k<=j)
dp[i][j]代表了1 ~ i中最后一个楼梯高度是b[j] 的最小花费,所以我们需要求出前面dp[i-1][1] ~ dp[i-1][j]的最小花费,在第二个循环中比较即可,用minn标记dp[i-1][1~(j-1)] 的最小花费。是一个很不错的办法
还学到了一个新函数去重函数unique。zbc
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define ll long long
#define bug(x) cout << #x << " == " << x << '\n'
const ll int MAX_N=2e3+5;
ll a[MAX_N] = { 0 };
ll b[MAX_N] = { 0 };
ll dp[MAX_N][MAX_N] = { 0 };
int main()
{
int n;
scanf("%d" ,&n);
for(int i=1;i<=n;i++)
{
scanf("%lld" ,&a[i]);
b[i]=a[i];
}
sort(b+1,b+1+n);
int m=unique(b+1,b+1+n)-(b+1);///去重
for(int i=1;i<=n;i++)///前i-1个数都已经完成计算啦,接下来我们要计算a[i]那个地方啦
{
ll minn=INT_MAX;
for(int j=1;j<=m;j++)
{
minn=min(minn,dp[i-1][j]);
///dp[i-1][j]是前面i-1个数,第i-1个数更改为b[j]时候,求出来的最小花费
///要和前面的dp[i-1][1]~dp[i-1][j-1]做一个比较哦!!!不然就会浪费钱~
dp[i][j]=minn+abs(a[i]-b[j]);///a[i]修改到b[i]啦
}
}
ll maxx=INT_MAX;
for(int i=1;i<=m;i++)
{
maxx=min(maxx,dp[n][i]);///当然要比较下a[n]修改到哪个值最划算啦
}
printf("%lld\n",maxx);
}
状压
cf1042BVitamins
①CF1042B Vitamins:一个简单的dp加二进制状压的题目~~
链接: 哎惹.
大意:数据有n组数,每组数有一个价值cic_ici和一个字符串S,字符串S中包含3个字母A,B,C,问集齐ABC三个字母的最小价值(一个字母可以有多个)
ps:由于很简单,是可以记录数据之类的做出来的,但是要练习dp嘛,所以写了下dp的写法
利用二进制表示不同的状态
001 C
010 B
011 BC
100 A
101 AC
110 AB
111 ABC
dp的数组大小开7(111)就可以啦
dp [i] [j] =max(d[i-1] [j],d[i-1][j-tmp] + a[tmp].value );
j表示当前状态(利用二进制表示)
i表示前i组数
我们可以通过&运算来判断是否能通过tmp到达j这个状态,
&运算:https://blog.csdn.net/xiaopihaierletian/article/details/78162863这个很清楚
运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
即:两位同时为“1”,结果才为“1”,否则为0
eg:101(AC)&100(A)!=0 而100—>101是可以的…
而且末状态的数值一定大于初状态:101>100
上代码啦~
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
#define bug(x) cout << #x << " == " << x << '\n'
const int MAX_N=1e3+5;
#define ll long long
struct node
{
int value;
string s;
int A,B,C;
} a[MAX_N];
int main()
{
int n;
scanf("%d",&n);
int dp[1<<3]= {0};
fill(dp,dp+(1<<3),1e8);
for(int i=1; i<=n; i++)
{
cin>>a[i].value>>a[i].s;
a[i].A=0;
a[i].B=0;
a[i].C=0;
if(a[i].s.find('A')!=-1)
{
a[i].A=1;
}
if(a[i].s.find('B')!=-1)
{
a[i].B=1;
}
if(a[i].s.find('C')!=-1)
{
a[i].C=1;
}
}
dp[0]=0;
for(int j=1; j<=n; j++)
{
for(int i=7; i>=1; i--)
{
/*int tmp=0;
if(a[j].A==1)
{
tmp+=4;
}
if(a[j].B==1)
{
tmp+=2;
}
if(a[j].C==1)
{
tmp+=1;
}
if(!(tmp&i))continue;
if(i>=tmp)
dp[i]=min(dp[i],dp[i-tmp]+a[j].value);
if(a[j].C==1)///001
{
if((1&i)&&i>=1)
dp[i]=min(dp[i],dp[i-1]+a[j].value);
}
if(a[j].B==1)///010
{
if((2&i)&&i>=2)
dp[i]=min(dp[i],dp[i-2]+a[j].value);
}*/
if(a[j].B&&a[j].C)///011
{
if((3&i)&&i>=3)
dp[i]=min(dp[i],dp[i-3]+a[j].value);
}
if(a[j].A==1)///100
{
if((4&i)&&i>=4)
dp[i]=min(dp[i],dp[i-4]+a[j].value);
}
if(a[j].A&&a[j].C)///101
{
if((5&i)&&i>=5)
dp[i]=min(dp[i],dp[i-5]+a[j].value);
}
if(a[j].A&&a[j].B)///110
{
if((6&i)&&i>=6)
dp[i]=min(dp[i],dp[i-6]+a[j].value);
}
if(a[j].A&&a[j].B&&a[j].C)///111
{
if((7&i)&&i>=7)
dp[i]=min(dp[i],dp[i-7]+a[j].value);
}
}
}
if(dp[7]==1e8)
{
printf("-1\n");
}
else
printf("%d\n",dp[7]);
}
被抹掉的代码是只能求出x个字符串组合刚好是ABC
比如AB+C,A+B+C之类的。那种AB+BC的一起组合就求不出来了,所以换了下代码
P1433 吃奶酪
第二题来啦!!!!
②P1433 吃奶酪
链接: lalala.
题目描述
房间里放着 nnn 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0)(0,0)点处。
输入格式
第一行有一个整数,表示奶酪的数量 n。
第 2到第 (n+1) 行,每行两个实数,第 (i+1) 行的实数分别表示第 i块奶酪的横纵坐标 xi,yi。
输出格式
输出一行一个实数,表示要跑的最少距离,保留 2位小数。
#include<bits/stdc++.h>
using namespace std;
#define bug(x) cout << #x << " == " << x << '\n'
const int MAX_N=1e3+5;
#define ll long long
struct node
{
double x,y;
} a[MAX_N];
double len(node a,node b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double dp[16][1<<16]= {0};
double way[20][20];
int n;
int main()
{
scanf("%d",&n);
a[0].x=0;
a[0].y=0;
for(int i=1; i<=n; i++)
{
scanf("%lf %lf",&a[i].x,&a[i].y);
}
memset(dp,127,sizeof(dp));///将浮点数dp最大化
for(int i=1; i<=n; i++)
{
for(int j=0; j<i; j++)
{
way[j][i] = len(a[i],a[j]);
way[i][j] = way[j][i];
}
}
for(int i=1; i<=n; i++)
{
dp[i][1<<(i-1)]=way[0][i];///从0到i的距离就是1<<(i-1)状态的最小路径
}
int N=n;
double maxx=INT_MAX;
for(int k=1; k<(1<<N); k++) //k表示当前状态
{
for(int i=1; i<=N; i++)///i表示当前状态的终点
{
if((k&(1<<(i-1)))==0)
continue;
for(int j=1; j<=N; j++)///j表示当前状态的上一个状态k-(1<<(i-1))的终点
{
if(i==j)
continue;
if((k&(1<<(j-1)))==0)
continue;
dp[i][k]=min(dp[i][k],dp[j][k-(1<<(i-1))]+way[i][j]);
}
if(k==((1<<n)-1))
{
maxx=min(maxx,dp[i][k]);
}
}
}
printf("%.2lf\n",maxx);
}
P5911 [POI2004]PRZ
第三题也来啦!!!P5911 [POI2004]PRZ
链接:看到这里了 还不收藏就是白嫖行为 谴责谴责.
题目背景
一只队伍在爬山时碰到了雪崩,他们在逃跑时遇到了一座桥,他们要尽快的过桥。
题目描述:
桥已经很旧了, 所以它不能承受太重的东西。任何时候队伍在桥上的人都不能超过一定的限制。 所以这只队伍过桥时只能分批过,当一组全部过去时,下一组才能接着过。队伍里每个人过桥都需要特定的时间,当一批队员过桥时时间应该算走得最慢的那一个,每个人也有特定的重量,我们想知道如何分批过桥能使总时间最少。
输入格式:
第一行两个数: W 表示桥能承受的最大重量和 n 表示队员总数。
接下来 n 行:每行两个数: t 表示该队员过桥所需时间和 w 表示该队员的重量。
输出格式:
输出一个数表示最少的过桥时间。
#include<bits/stdc++.h>
using namespace std;
#define bug(x) cout << #x << " == " << x << '\n'
const int MAX_N=1e5+5;
#define ll long long
struct node
{
int t,w;
bool operator <(node a)
{
if(w!=a.w)
{
return w<a.w;
}
return t<a.t;
}
}a[40];
int dp[MAX_N]={0};
int tt[MAX_N]={0};
int ww[MAX_N]={0};
int main()
{
int w,n;
scanf("%d %d",&w,&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&a[i].t,&a[i].w);
}
int tot=((1<<n));
for(int i=1;i<tot;i++)///每一种状态的枚举
{
for(int j=1;j<=n;j++)///判断队员j是不是在这个状态里面????
{
if(((1<<(j-1))&i))///如果在这个状态里面的话
{
tt[i]=max(tt[i],a[j].t);///找出这个状态里的队员他们算一个组的时候需要的最大时间
ww[i]+=a[j].w;///这个状态下队员的总重量
}
}
}
memset(dp,10000,sizeof(dp));
dp[0]=0;///没有队员的时候时间是0
for(int i=1;i<tot;i++)
{
for(int j=i;;j=i&(j-1))///可以通过这个样子枚举i状态的每一个子状态,不明觉厉!!!
{
if(ww[i^j]<=w)///(i^j)^j=i且0^1=1,1^0=1,0^0=1,1^1=0所以i^j指的是在i状态存在但是在j状态下不存在的队员
{
dp[i]=min(dp[i],dp[j]+tt[i^j]);///tt[i^j]其他队员组一个组过桥的时间
}
if(j==0)break;///枚举完啦
}
}
printf("%d\n",dp[tot-1]);
}
1.关于for( j=i ; ; j = i & (j-1) )
运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
e.g:i=1011;
j=1011;
j=1011&1010=1010;
j=1011&1001=1001;
j=1011&1000=1000;
j=1011&0111=0011;
j=1011&0010=0010;
j=1011&0001=0001;
枚举了每一种子状态
2.关于(i ^ j) ^ j = i非常好用,因为异或运算满足自反性。
第四题:P3052 [USACO12MAR]Cows in a Skyscraper G
P3052 [USACO12MAR]Cows in a Skyscraper G
链接:收藏一下吧~.
#include<bits/stdc++.h>
using namespace std;
#define bug(x) cout << #x << " == " << x << '\n'
const int MAX_N=1e5+5;
#define ll long long
int w[30]= {0};
ll dp[1<<19]= {0};///每一种状态下的电梯数
int reminder[1<<19];///该状态下 所有电梯的最大剩余量
int main()
{
int n,maxx;
scanf("%d%d",&n,&maxx);
for(int i=1; i<=n; i++)
{
scanf("%d",&w[i]);
}
int tot=1<<n;
fill(dp,dp+(1<<n),1e6);
dp[0]=0;
reminder[0]=maxx;///状态为0的时候,一只牛都没有哦,所以要初始化一下子
for(int i=1;i<tot;i++)///又开始枚举每一种状态啦
{
for(int j=1;j<=n;j++)///塞奶牛
{
int now=i|(1<<(j-1));
///i状态加上j号牛now状态啦,|运算0|1=1,1|0=1,1|1=1,0|0=0
if(reminder[i]>=w[j]&&dp[now]>=dp[i])
{
reminder[now]=max(reminder[i]-w[j],reminder[i]);
dp[now]=dp[i];
}
else if(reminder[i]<w[j]&&dp[now]>=dp[i]+1)
{
reminder[now]=max(reminder[i],maxx-w[j]);
dp[now]=dp[i]+1;
}
}
}
printf("%lld\n",dp[tot-1]);
}
ps:位运算真的好好用,这一题就利用了位运算的|通过这个枚举出来每一个状态的下一个状态,不得不说非常机智啦,之前写的每一个状态去找上一个状态和他也是异曲同工之妙,但是我还需要判断一下,这个就完全不用啦。
一开始卡测试点发现是因为可能会存在一个状态但是记录的reminder不是最小的情况,修改后在dp[i]相同的情况下也要修改reminder[i]的值使它变成当前状态的最大电梯剩余量然后就快乐ac啦开心!!!!
数位dp
(HDU)不要62(板子)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll int maxn =30;
int a[maxn] = { 0 };
///a代表每一位的数
ll dp[maxn][2] = { 0 };
///i代表位数
int pos = 0;
ll dfs(int pos,int pre,int state,int limit)
{
if(!pos)return 1;
if( (!limit) && dp[pos][state]!=-1)
return dp[pos][state];
///如果没有限制条件,直接返回dp即可;
int up = 9;///该位0~9的枚举
if(limit)
{
up = a[pos];///该位只能枚举到0~a[pos]
}
ll ans = 0;
for(int i=0;i<=up;i++)
{
if(pre==6&&i==2)continue;
if(i==4)continue;
ans += dfs(pos-1,i,i==6,limit&&i==a[pos]);
///如果上一位是限制的,这一位也是到限制临界点,那么下一位也是限制的
}
if(!limit)dp[pos][state]=ans;
///dp专门存储没有限制的值
return ans;
}
ll slove(int x)
{
pos=0;
while(x)
{
pos++;
a[pos] = x%10;
x/=10;
}
return dfs(pos,-1,0,true);
///哪一位,前面一位的值,前面一位是不是6,有没有限制条件
}
int main()
{
int l,r;
memset( dp,-1,sizeof(dp) );
while( scanf("%d %d",&l,&r) && (l+r) )
{
printf( "%lld\n",slove(r)-slove(l-1) );
}
}
(HDU)F(x)
///发现,数位dp往往是根据条件,修改dfs需要的变量定义
///而dp是用来存储没有区间限制条件的计算出来的值
///limit是用来判断区间,防止计算的值超过了区间,是一个模板了
///面对数位问题,重点是通过条件,准确地构建方程还有适当优化
///在这一题中,如果dp[maxn][sum][maxx]肯定会超出内存
///这个时候,发现该题目只需要sum<=maxx的那一部分,因为这是题目条件
///因此dp降到了二维
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll int maxn = 12;
const ll int N = 1e5+10;
#define bug(x) cout<<#x<<"=="<<x<<endl;
int a[maxn] = { 0 };
///a代表每一位的数
ll dp[maxn][N] = { 0 };
///i代表位数
int pos = 0;
ll maxx = 0;
inline ll f(ll x)
{
if(!x)return 0;
return f(x/10)*2+x%10;
}
ll dfs(int pos,ll sum,bool limit)
{
if(maxx-sum<0)return 0;
///当前数超过了f(a)不能取
if(!pos)
{
return sum<=maxx;
///每一位都取过了,判断是不是满足条件即可
}
if(!limit&& (dp[pos][maxx-sum]!=-1) )
{
return dp[pos][maxx-sum];
}
int up=9;
if(limit)
{
up=a[pos];
}///板子,防止区间过界
ll ans=0;
for(int i=0; i<=up; i++)
{
ans+=dfs(pos-1,sum+i*( 1<<(pos-1) ),limit&&i==a[pos]);
}
if(!limit)
{
dp[pos][maxx-sum]=ans;
}
return ans;
}
ll slove(int x)
{
pos=0;
while(x)
{
pos++;
a[pos] = x%10;
x/=10;
}
return dfs(pos,0,true);
///第二个表示前面花费了sum
}
int main()
{
ll a,b;
int T_T;
scanf("%d ",&T_T);
int cas=0;
memset( dp,-1,sizeof(dp) );
while(T_T--)
{
scanf("%lld %lld",&a,&b);
cas++;
maxx=f(a);
printf( "Case #%d: %lld\n",cas,slove(b));
}
}
[USACO06NOV] Round Numbers S
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
#define ll long long
const ll int maxn = 120;
const ll int N = 1e5+10;
#define bug(x) cout<<#x<<"=="<<x<<endl;
int a[maxn] = { 0 };
int pos=0;
ll dp[maxn][70]= {0};
ll dfs(int pos,int sum,bool qd0,bool limit)
{
if(!pos)
{
return sum>=32;
}
///有无前导0,我存储的dp应该是没有前导0的
if( !limit && !qd0 && dp[pos][sum]!=-1)
{
return dp[pos][sum];
}
int up=1;
if(limit)
{
up = a[pos];
}
ll ans = 0;
for(int i=0; i<=up; i++)
{
if(i==0&&qd0)
{
ans += dfs(pos-1,sum,true,limit&&i==a[pos]);
///有前导0的时候,统计的个数不改变
}
else if(i==1)
{
ans += dfs(pos-1,sum-1,false,limit&&i==a[pos]);
}
else if(i==0)
{
ans += dfs(pos-1,sum+1,false,limit&&i==a[pos]);
}
}
if( !limit && !qd0 )
{
dp[pos][sum]=ans;
}
return ans;
}
ll slove(ll x)
{
pos=0;
while(x)
{
pos++;
a[pos] = x%2;
x /= 2;
}
return dfs(pos,32,true,true);
///中间值代表sum0-sum1可能会存在负数,所以加上32
}
int main()
{
ll r,l;
scanf("%lld %lld",&l,&r);
memset( dp,-1,sizeof(dp) );
cout<<( slove(r)-slove(l-1) )<<endl;
}
(HDU)Balanced Number
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
#define ll long long
const ll int maxn = 120;
const ll int N = 1e5+10;
#define bug(x) cout<<#x<<"=="<<x<<endl;
int pos=0;
int a[maxn] = { 0 };
ll dp[20][2000][20];
///i代表当前位,j代表sum,
ll dfs(int pos,int sum,int cen,bool limit)
{
if(!pos)
{
return sum==0;
}
if( !limit && dp[pos][sum][cen]!=-1)
{
return dp[pos][sum][cen];
}
ll up=9;
if(limit)
{
up=a[pos];
}
ll ans = 0;
for(int i=0;i<=up;i++)
{
ans += dfs(pos-1,sum+(pos-cen)*i,cen,limit&&i==a[pos]);
}
if(!limit)
{
dp[pos][sum][cen]=ans;
}
return ans;
}
ll slove(ll x)
{
if(x==-1)return 0;
pos = 0;
while(x)
{
pos++;
a[pos] = x%10;
x /= 10;
}
ll ans = 0;
for(int i=1;i<=pos;i++)
{
ans += dfs(pos,0,i,true);
///i为跷跷板的中点
///0为(左边跷跷板的值-右跷跷板的值)
///pos为当前位数
}
return ans-(pos-1);
///因为每一次计算会把 0,00,000,0000....当成不同的数进行记录
///所以需要减去这些多余的值
}
int main()
{
int T_T;
scanf("%d",&T_T);
ll l,r;
memset( dp,-1,sizeof(dp) );
while(T_T--)
{
scanf("%lld %lld",&l,&r);
printf("%lld\n", slove(r)-slove(l-1) );
}
}
(cf)55D
链接:https://codeforces.com/problemset/problem/55/D
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll int maxn = 120;
const ll int N = 1e5+10;
#define bug(x) cout<<#x<<"=="<<x<<endl;
int pos=0;
const ll mod=2520;
///因为1~9的最小公倍数为5040
int a[30]= {0};
ll dp[30][2530][50]= {0};
int yz[2530] = { 0 };
///j代表前面n-pos位每一位相加得到的值%mod;
///2520最多可以分为48个因子
///最后只需要判断j%yz[k]==0即可
inline void js()
{
int tot = 0;
for(int i=1; i<=2520; i++)
{
if(2520%i==0)
{
tot++;
yz[i] = tot;
}
}
}
inline int _lcm(int a,int b)
{
return a*b/__gcd(a,b);
}
ll dfs(int pos,int he,int lcm,bool limit)
{
if(!pos)
{
return he%lcm==0;
}
if(!limit&dp[pos][he][ yz[lcm] ]!=-1)
{
return dp[pos][he][ yz[lcm] ];
}
int up=9;
if(limit)
{
up=a[pos];
}
ll ans = 0;
for(int i=0; i<=up; i++)
{
int nextlcm=lcm;
if(i)
nextlcm = _lcm(lcm,i);
int nexthe=(he*10+i)%mod;
ans += dfs(pos-1,nexthe,nextlcm,limit&&i==a[pos]);
}
if(!limit)
{
dp[pos][he][ yz[lcm] ]=ans;
}
return ans;
}
ll slove(ll x)
{
pos=0;
while(x)
{
pos++;
a[pos]=x%10;
x/=10;
}
return dfs(pos,0,1,true);
}
int main()
{
js();
int T_T;
scanf("%d",&T_T);
memset(dp,-1,sizeof(dp) );
ll l,r;
while(T_T--)
{
cin>>l>>r;
cout<<slove(r)-slove(l-1)<<'\n';
}
}
Bomb
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll int maxn = 120;
const ll int N = 1e5+10;
#define bug(x) cout<<#x<<"=="<<x<<endl;
int a[maxn] = { 0 };
ll dp[maxn][10][2] = { 0 };
int pos=0;
ll dfs(int pos,int pre,bool yep,bool limit)
{
if(pos==0)
{
return yep==1;
}
if((!limit)&&(dp[pos][pre][yep]!=-1))
{
return dp[pos][pre][yep];
}
int up=9;
if(limit)
{
up=a[pos];
}
ll ans=0;
for(int i=0; i<=up; i++)
{
ans+=dfs(pos-1,i,(pre==4&&i==9)||yep,limit&&i==a[pos]);
}
if(!limit)
{
dp[pos][pre][yep]=ans;
}
return ans;
}
ll slove(ll x)
{
if(x==0)return 0;
pos = 0;
while(x)
{
pos++;
a[pos] = x%10;
x /= 10;
}
ll ans=dfs(pos,0,false,true);
return ans;
}
signed main()
{
ll r;
int T_T;
scanf("%d",&T_T);
memset(dp,-1,sizeof(dp));
while(T_T--)
{
cin>>r;
cout<<slove(r)<<'\n';
}
}
(HDU)B-number
和上面同理就加了一个维度
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll int maxn = 120;
const ll int N = 1e5+10;
#define bug(x) cout<<#x<<"=="<<x<<endl;
int a[maxn] = { 0 };
ll dp[maxn][20][10][2] = { 0 };
int pos=0;
ll dfs(int pos,int pre,int res,bool yep,bool limit)
{
if(pos==0)
{
if(res%13==0)
return yep==1;
else
return 0;
}
if((!limit)&&(dp[pos][res][pre][yep]!=-1))
{
return dp[pos][res][pre][yep];
}
int up=9;
if(limit)
{
up=a[pos];
}
ll ans=0;
for(int i=0; i<=up; i++)
{
ans+=dfs(pos-1,i,(res*10+i)%13,(pre==1&&i==3)||yep,limit&&i==a[pos]);
}
if(!limit)
{
dp[pos][res][pre][yep]=ans;
}
return ans;
}
ll slove(ll x)
{
if(x==0)return 0;
pos = 0;
while(x)
{
pos++;
a[pos] = x%10;
x /= 10;
}
ll ans=dfs(pos,0,0,false,true);
return ans;
}
signed main()
{
ll r;
///int T_T;
///scanf("%d",&T_T);
memset(dp,-1,sizeof(dp));
while(T_T--)
while(cin>>r&&r)
{
cout<<slove(r)<<'\n';
}
}
(HDU)XHXJ’s LIS
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll int maxn = 120;
const ll int N = 1e5+10;
#define bug(x) cout<<#x<<"=="<<x<<endl;
ll dp[30][3000][30] = { 0 };
int a[30] = { 0 };
int pos = 0;
int k;
inline bool check(int x)
{
bitset<15> bits = x;
return bits.count()==k;
///数组里面有多少个1
}
inline int get_next(int state,int x)///两个等效
{
for(int i=x;i<=9;i++)
{
if((1<<i)&state)
{
return ( state^(1<<i) )|(1<<x);
}
}
return state|(1<<x);
}
/*inline int get_next(int state,int now)
{
bitset<15> bits=state;
int i=now;
for(; i<=9; i++)
{
if(bits[i]==1)
{
break;
}
}
if(i!=10)
{
bits[i] = 0;
}
bits[now]=1;
///修改最长序列的末尾值
int ans=bits.to_ulong();
///讲二进制的数组转换成unsigned long的整数型
return ans;
}*/
ll dfs(int pos,int state,bool lead,bool limit)
{
if(!pos) return check(state);
if( !limit&&dp[pos][state][k]!=-1) return dp[pos][state][k];
int up = limit ? a[pos]:9 ;
///上下等效
/*
int up=9;
if(limit)
{
up=a[pos];
}*/
ll ans = 0;
for(int i=0; i<=up; i++)
{
ans +=dfs(pos-1, lead&&i==0 ? 0:get_next(state,i) , lead&&i==0,limit&&i==a[pos]);
///上下等效
/*if(lead&& i==0 )
{
ans += dfs(pos-1,state,lead&& i==0,limit&&i==a[pos]);
}
else
{
int next_state=get_next(state,i);
ans += dfs(pos-1,next_state,lead&& i==0,limit&&i==a[pos]);
}*/
}
if(!limit)
{
dp[pos][state][k] = ans;
}
return ans;
}
ll slove(ll x)
{
if(x==0)return 0;
pos = 0;
while(x)
{
pos++;
a[pos] = x%10;
x /= 10;
}
return dfs(pos,0,true,true);
}
signed main()
{
ll l,r;
int T_T;
scanf("%d",&T_T);
memset(dp,-1,sizeof(dp));
int tot=0;
while(T_T--)
{
tot++;
cin>>l>>r>>k;
printf("Case #%d: ",tot);
cout<<slove(r)-slove(l-1)<<'\n';
}
}
kuangbin专题dp入门
(HDU)Max Sum Plus Plus(有点麻烦)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=1e6+10;
const ll int INF=0x7fffffff;
int a[maxn] = { 0 };
int dp[maxn] = { 0 };
///前i个数分组
int maxx[maxn] = { 0 };
///maxx[j]表示前i-1个数,分组为j个的最大值
int main()
{
int n,m;
while(scanf("%d %d",&m,&n)!=EOF)
{
dp[0] = 0;
maxx[0] = 0;
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
dp[i] = 0;
maxx[i] = 0;
}
int maxxnow=-INF;
for(int j=1; j<=m; j++)///分为j组
{
maxxnow=-INF;
for(int i=j; i<=n; i++)///前i个数,至少要有j个数才可以分成j组
{
dp[i]=max(dp[i-1],maxx[i-1])+a[i];
///当前的dp[i-1]表示的是前i-1个数被分为j个组,a[i]在第j个组
///maxx[i-1]表示的是当前i-1个数被分为j组,然后a[i]为第j组
maxx[i-1]=maxxnow;
///当i=j的时候,i-1个数无法分j组当然是-INF啦
///利用循环更新maxx的值,为下一次dp循环做准备
maxxnow=max(maxxnow,dp[i]);
}
}
printf("%d\n",maxxnow);
}
}
(HDU)Doing Homework
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=20;
const ll int INF=0x7fffffff;
struct node
{
string name;
int d,c;
}way[maxn];
ll dp[ (1<<maxn) ] = { 0 };
int t[ (1<<maxn) ] = { 0 };
int path[ (1<<maxn) ]={0};
int tot;
void print(int i)
{
if(path[i]==0||i==0)return ;
int before=1<<(path[i]-1);
print(i-before);
cout<<way[ path[i] ].name<<'\n';
}
inline void slove()
{
int n;
scanf("%d",&n);
getchar();
for(int i=1; i<=n; i++)
{
cin>>way[i].name>>way[i].d>>way[i].c;
}
tot = (1<<n);
path[0] = 0;
for(int i=1;i<tot;i++)
{
dp[i] = INF;
path[i] = 0;
for(int j=1;j<=n;j++)
{
int before = 1<<(j-1);
if(i&before)
{
int nowtime = t[i^before] + way[j].c-way[j].d;
if( nowtime <= 0 )
{
nowtime = 0;///会罚钱的时间
}
if(nowtime+dp[i^before]<=dp[i])///1~n按的是字典树增大的顺序,所以值一样的时候,将字典序大的科目 放在每次状态的最后
{
dp[i] = nowtime+dp[i^before];
t[i] = t[i^before] + way[j].c;
path[i] =j;
}
}
}
}
printf("%lld\n",dp[tot-1]);
print(tot-1);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
slove();
}
}
(HDU)Super Jumping! Jumping! Jumping!(水题,最大上升子序列的变异体)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=2e3+10;
const ll int INF=0x7fffffff;
int a[maxn] = { 0 };
ll dp[maxn] = { 0 };
int main()
{
int n;
while(scanf("%d",&n)!=EOF,n)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
ll maxx = 0;
for(int i=1;i<=n;i++)
{
dp[i] = a[i];
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
{
dp[i]=max(dp[i],a[i]+dp[j]);
}
}
maxx=max(dp[i],maxx);
}
printf("%lld\n",maxx);
}
}
(HDU)Piggy-Bank(背包问题,无限背包,这波就完事了~)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=2e5+10;
const ll int INF=0x3f3f3f3f3f3f3f3f3f;
int n;
int tot = 0;
int sum = 0;
ll p[maxn] = { 0 };
ll w[maxn] = { 0 };
ll dp[maxn] = { 0 };
void slove()
{
ll bw,ew;
scanf("%lld %lld",&bw,&ew);
ll totw = ew-bw;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&p[i],&w[i]);
}
memset( dp,INF,sizeof(dp) );
/// bug(dp[1]);
///bug(INF);
dp[0]=0;
for(int i=1;i<=n;i++)
{
for(int j=w[i];j<=totw;j++)
{
dp[j]=min(dp[j],dp[ j-w[i] ]+p[i]);
}
}
if(dp[totw]==INF)
{
printf("This is impossible.\n");
return ;
}
printf("The minimum amount of money in the piggy-bank is %lld.\n",dp[totw]);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
slove();
}
}
(HDU)免费馅饼
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=1e5+10;
ll mp[maxn][12]={0};///防止数组越界
ll dp[maxn][12]={0};
int main()
{
int n;
while(scanf("%d",&n)!=EOF,n)
{
int T = 0;
memset(mp,0,sizeof(mp) );
memset(dp,0,sizeof(dp) );
for(int i=1; i<=n; i++)
{
int t,pos;
scanf("%d %d",&pos,&t);
mp[t][pos]++;
T=max(T,t);
}
for(int i=T;i>=0;i--)
///倒着搜,dp[i][j]就是从T到i秒倒着走到j点时,最大的获得馅饼数
///这样子就没有起点的限制,更加方便,最后的终点就是dp[0][5];
{
for(int pos=0;pos<=10;pos++)
{
dp[i][pos]=max(dp[i+1][pos],dp[i+1][pos+1]);
if(pos)///同防止越界
{
dp[i][pos]=max(dp[i][pos],dp[i+1][pos-1]);
}
dp[i][pos]+=mp[i][pos];
///注意加上自己所处位置的馅饼哦~
}
}
printf("%lld\n",dp[0][5]);
}
}
Tickets(线性dp,普普通通)
每一元素只有两种情况,直接取或者和前面一个一起取
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=2e3+10;
const ll int INF=0x3f3f3f3f;
int a[maxn] = { 0 };///单个a[i]s所花的时间
int hb[maxn] = { 0 };///合并两个a后花的时间
ll dp[maxn] = { 0 };
inline void slove()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n-1;i++)
{
scanf("%d",&hb[i]);
///合并i和i+1;
}
dp[0] = 0;
dp[1] = a[1];
for(int i=2;i<=n;i++)
{
dp[i]=min(dp[i-1]+a[i],dp[i-2]+hb[i-1]);
}
ll ans=dp[n];
int h=8+ans/3600;
int m=ans/60%60;
int s=ans%60;
if(h>12)
{
printf("%02d:%02d:%02d pm\n",h-12,m,s);
}
else
{
printf("%02d:%02d:%02d am\n",h,m,s);
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
slove();
}
}
最少拦截系统 (根据dilworth定理)
不下降子序列最小个数等于最大上升子序列的长度
不上升子序列的最小个数大于最大下降子序列的长度
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=2e3+10;
const ll int INF=0x3f3f3f3f;
int a[maxn] = { 0 };
int dp[maxn] = { 0 };
int maxx = 0;
int main()
{
int n;
while( scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]); dp[i]=1;
}
maxx = 0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
{
dp[i]=max(dp[i],dp[j]+1);
}
}
maxx=max(maxx,dp[i]);
}
printf("%d\n",maxx);
}
}
(HDU)FatMouse’s Speed(寻找dp路径的好办法,相当nice,类似于递归的操作)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define bug(x) cout<<#x<<" == "<<x<<endl;
const int maxn=2e3+10;
const ll int INF=0x3f3f3f3f;
int tot = 0;
int dp[maxn] = { 0 };
struct node
{
int w,s;
int index;
bool operator<(node b)
{
if(w!=b.w)
{
return w<b.w;///贪心一波
}
}
} a[maxn];
vector<int> way;
int main()
{
int w,s;
while(scanf("%d %d",&w,&s)!=EOF)
{
tot++;
a[tot].index = tot;
a[tot].w = w;
a[tot].s = s;
///if(tot==9)break;
}
sort(a+1,a+1+tot);
int maxt=1;
for(int i=1; i<=tot; i++)
{
dp[i] = 1;
for(int j=1; j<i; j++)
{
if(a[j].w<a[i].w&&a[j].s>a[i].s)
{
dp[i]=max(dp[i],dp[j]+1);
if(dp[i]>dp[maxt])
maxt=i;
}
}
}
printf("%d\n",dp[maxt]);
int sum=dp[maxt];
way.push_back(maxt);
for(int i=maxt-1;i>=1;i--)
{
if(dp[maxt]==dp[i]+1)
{
way.push_back(i);
maxt = i;
}
}
for(int i=sum-1;i>=0;i--)
{
int v=way[i];
printf("%d\n",a[v].index);
}
}
FatMouse and Cheese(记忆化搜索)
#include<iostream>
#include<cstdio>
#include<string>
#define bug(x) cout<<#x<<" == "<<x<<endl;
#define ll long long
using namespace std;
const int maxn=3e3+10;
#define INF 0x3f3f3f3f
int a[maxn][maxn] = { 0 };
int dp[maxn][maxn] = { 0 };
int dx[4]= {0,0,1,-1};
int dy[4]= {1,-1,0,0};
int n,k;
bool judge(int x,int y)
{
return x>=0&&x<n&&y>=0&&y<n;
}
int dfs(int x,int y)
{
if(dp[x][y])return dp[x][y];
int maxx=0;
for(int i=0; i<4; i++)
{
for(int j=1; j<=k; j++)
{
int tx=x+dx[i]*j;
int ty=y+dy[i]*j;
if(judge(tx,ty)&&a[tx][ty]>a[x][y])
{
int now = dfs(tx,ty);
maxx=max(maxx,now);
}
}
}
dp[x][y]=maxx+a[x][y];
return dp[x][y];
}
int main()
{
while(scanf("%d %d",&n,&k)!=EOF&&n!=-1&&k!=-1)
{
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
dp[i][j]=0;
scanf("%d",&a[i][j]);
}
}
dfs(0,0);
cout<<dp[0][0]<<'\n';
}
}
(HDU)Phalanx
#include<bits/stdc++.h>
#include<string>
#define bug(x) cout<<#x<<" == "<<x<<endl;
#define ll long long
using namespace std;
const int maxn = 2e3 + 10;
#define INF 0x3f3f3f3f
char a[maxn][maxn] = { 0 };
int dp[maxn][maxn] = { 0 };
int main()
{
int n;
while(scanf("%d",&n)!=EOF && n)
{
if(n==0)break;
for(int i=0; i<n; i++)
scanf("%s",a[i]);
memset(dp,0,sizeof(dp));
int ans=1;
for(int i=0; i<n; i++)
{
for(int j=n-1; j>=0; j--)
{
dp[i][j] = 1;
if( i == 0 || j == n-1) continue;
int t = dp[i-1][j+1];
for(int k=1; k<=t; k++)
{
if(a[i-k][j] == a[i][k+j] ) dp[i][j]++;
else break;
}
ans = max(dp[i][j],ans);
}
}
cout<<ans<<endl;
}
}