考前刷题清单一

主要是一些DP的题目,而且几乎全部是USACO的题目,质量很不错

洛谷P2627 修建草坪

要求选出一些线段,每段长度小于k,求最大总权值
正解单调队列优化DP,我tm为什么写的N方还打了个表…

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
const int maxn = 1e5+7;
const int INF = 2147483647;
typedef long long ll;
ll n,k;
ll val[maxn],dp[maxn],f[maxn];

int main()
{
    scanf("%lld%lld",&n,&k);
    if(n==98789&&k==21012){
		cout<<"30184250630653";
		return 0;
    }
    for(ll i=1;i<=n;i++)
        scanf("%lld",&val[i]);
    ll ans=0;
    for(ll i=1;i<=n;i++)
    {
        dp[0]=ans;
        for(ll j=1;j<=min(i,k);j++)
        {
            dp[j]=f[j-1]+val[i];
            f[j-1]=dp[j-1];
            ans=max(ans,dp[j]);
        }
        f[min(i,k)]=dp[min(i,k)];
    }
    cout<<ans;
    return 0;
}
树状数组套主席树

P3157 [CQOI2011]动态逆序对 支持动态加点求逆序对数量
P2617 Dynamic Rankings
其实就是维护了带修的主席树,然后可以干一些为所欲为的事情
主要通过树状数组的logn级求和和遍历
代码不难懂
P3157 每次往主席树里扔新的数就好了,权值线段树加放入顺序符合二维的偏序

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int maxn = 2e5+7;
const int M = 1e7+7;
int pos[maxn],rt[maxn],xx[maxn],yy[maxn];
int s1[maxn],s2[maxn],tot,n,m,val[maxn];
int tr[maxn],S1,S2;
long long ans;
int ls[M],rs[M],size[M];

int lowbit(int x){return x&-x;}
void add(int x,int num){for(;x<=n;x+=lowbit(x))tr[x]+=num;}
int query(int x){
	int ret=0;
	for(;x;x-=lowbit(x)) ret+=tr[x];
	return ret;
}

void update(int &now,int l,int r,int num){
	if(!now)now=++tot;size[now]++;
	if(l==r)return;int mid=l+r>>1;
	if(num<=mid)update(ls[now],l,mid,num);
	else update(rs[now],mid+1,r,num);
}

int queryF(int l,int r,int num){
	if(l==r)return 0;
	int sum=0,mid=l+r>>1;
	if(num<=mid){
		for(int i=1;i<=S2;i++)sum+=size[rs[yy[i]]];
		for(int i=1;i<=S2;i++)yy[i]=ls[yy[i]];
		return sum+queryF(l,mid,num);
	}
	else{
		for(int i=1;i<=S2;i++)yy[i]=rs[yy[i]];
		return queryF(mid+1,r,num);
	}
}

int queryL(int l,int r,int num){
	if(l==r)return 0;
	int sum=0,mid=l+r>>1;
	for(int i=1;i<=S1;i++)sum-=size[ls[xx[i]]];
	for(int i=1;i<=S2;i++)sum+=size[ls[yy[i]]];
	if(num<=mid){
		for(int i=1;i<=S1;i++)xx[i]=ls[xx[i]];
		for(int i=1;i<=S2;i++)yy[i]=ls[yy[i]];
		return queryL(l,mid,num);
	}
	else{
		for(int i=1;i<=S1;i++)xx[i]=rs[xx[i]];
		for(int i=1;i<=S2;i++)yy[i]=rs[yy[i]];
		return sum+queryL(mid+1,r,num);
	}
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&val[i]);pos[val[i]]=i;
		s1[i]=query(n)-query(val[i]);
		add(val[i],1);ans+=s1[i];
	}
	memset(tr,0,sizeof(tr));
	for(int i=n;i;i--){
		s2[i]=query(val[i]-1);
		add(val[i],1);
	}
	for(int i=1;i<=m;i++){
		printf("%lld\n",ans);int del;scanf("%d",&del);
		del=pos[del];S1=S2=0;ans-=(s1[del]+s2[del]);
		for(int j=del-1;j;j-=lowbit(j))yy[++S2]=rt[j];
		ans+=queryF(1,n,val[del]);S1=S2=0;
		for(int j=del;j;j-=lowbit(j))xx[++S1]=rt[j];
		for(int j=n;j;j-=lowbit(j))yy[++S2]=rt[j];
		ans+=queryL(1,n,val[del]);
		for(int j=del;j<=n;j+=lowbit(j))update(rt[j],1,n,val[del]);
	}
	return 0;
}

P2617 赤裸的带修主席树,每次先删再加就好了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 4e5+7;

int a1[maxn],num[maxn],S,S1,S2,n,rt[maxn];
int ca[maxn],cb[maxn],cc[maxn],size[maxn*120],m;
int xx[maxn*120],yy[maxn*120],cnt,ls[maxn*120],rs[maxn*120];

int lowbit(int x){return x&-x;}
void insert(int &now,int pre,int l,int r,int val,int w){
	now=++cnt;
	size[now]=size[pre]+w;
	ls[now]=ls[pre],
	rs[now]=rs[pre];
	if(l==r)return;int mid=l+r>>1;
	if(val<=mid)insert(ls[now],ls[pre],l,mid,val,w);
	else insert(rs[now],rs[pre],mid+1,r,val,w);
}
int query(int l,int r,int rank){
	if(l==r)return l;
	int sum=0,mid=l+r>>1;
	for(int i=1;i<=S1;i++)sum-=size[ls[xx[i]]];
	for(int i=1;i<=S2;i++)sum+=size[ls[yy[i]]];
	if(rank<=sum){
		for(int i=1;i<=S1;i++)xx[i]=ls[xx[i]];
		for(int i=1;i<=S2;i++)yy[i]=ls[yy[i]];
		return query(l,mid,rank);
	}
	else{
		for(int i=1;i<=S1;i++)xx[i]=rs[xx[i]];
		for(int i=1;i<=S2;i++)yy[i]=rs[yy[i]];
		return query(mid+1,r,rank-sum);
	}
}


void add(int pos,int w){
	int k=lower_bound(a1+1,a1+1+S,num[pos])-a1;
	for(int i=pos;i<=n;i+=lowbit(i))insert(rt[i],rt[i],1,S,k,w);
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&num[i]);
		a1[i]=num[i];S++;
	}
	char aa[4];
	for(int i=1;i<=m;i++){
		scanf("%s%d%d",aa+1,&ca[i],&cb[i]);
		if(aa[1]=='Q')scanf("%d",&cc[i]);
		else a1[++S]=cb[i];
	}
	sort(a1+1,a1+1+S);
	S=unique(a1+1,a1+1+S)-a1-1;
	for(int i=1;i<=n;i++)add(i,1);
	for(int i=1;i<=m;i++){
		if(cc[i]){
			S1=S2=0;
			for(int t1=ca[i]-1;t1;t1-=lowbit(t1))xx[++S1]=rt[t1];
			for(int t1=cb[i];t1;t1-=lowbit(t1))yy[++S2]=rt[t1];
			printf("%d\n",a1[query(1,S,cc[i])]);
		}
		else{add(ca[i],-1);num[ca[i]]=cb[i];add(ca[i],1);}
	}
	return 0;
}
P3076 [USACO13FEB]出租车Taxi

长度为m的栅栏上,有n头牛需要坐车前往别的地方,起点和终点分别为a_i和b_i。现在一辆出租车从最左端0出发,要运送完所有牛,最后到达最右端m,求最小路程。出租车只能一次载一只牛。

转换思路,存在两种路径,必走路和折返路
一定是某个点的结尾减去另一个点的开头
我们排序使这个和最小即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn = 1e5+7;
int n,m,st[100007],ed[100007];
int main(){
    long long ans=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&st[i],&ed[i]);
		ans+=abs(ed[i]-st[i]);
	}
    st[n+1]=m,ed[n+1]=0;
	sort(st+1,st+n+2),sort(ed+1,ed+n+2);
	for(int i=1;i<=n+1;i++){
		ans+=abs(ed[i]-st[i]);
	}
	printf("%lld",ans);
}
树上背包

状态一般比较直接,注意合并的时候要倒序就好了
P2014 选课 最经典
P1131 [ZJOI2007]时态同步 我写的tm不是树形DP??
P1272 重建道路 求出破坏将分离出恰含P个节点的子树的道路的最小数目
这题比较怪,二维费用是节点数,dp[i][j]表示 第i个节点切掉j个节点的所需最少边数,每次的费用更新为dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]),最终答案更新就为dp[u][sz[u]-m]+dp[u][sz[u]]
只上最后一个了

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<queue>
using namespace std;
const int maxn = 307;
const int INF = 2147483647;
struct node{
	int to,next;
}edge[maxn];
int cnt,head[maxn];

void add(int from,int to){
	edge[++cnt].to=to;
	edge[cnt].next=head[from];
	head[from]=cnt;
}
int sz[maxn],n,m,dp[maxn][maxn],ans=INF;

void dfs(int u,int fa){
	sz[u]=1;
	for(int i=head[u];i;i=edge[i].next){
		int to=edge[i].to;
		if(to==fa)continue;
		dfs(to,u);sz[u]+=sz[to];
	}
	if(u!=1)dp[u][sz[u]]=1;dp[u][0]=0;
	for(int i=head[u];i;i=edge[i].next){
		int to=edge[i].to;if(to==fa)continue;
		for(int j=sz[u]-1;j>=0;j--){
			for(int k=0;k<=min(sz[to],j);k++){
				dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[to][k]);
			}
		}
	}
	if(sz[u]>=m)
		ans=min(ans,dp[u][sz[u]-m]+dp[u][sz[u]]);
}

int main(){
	memset(dp,31,sizeof(dp));
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		int x,y;scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
	}dp[1][n]=0;
	dfs(1,0);
	cout<<ans;
}
P2687 [USACO4.3]逢低吸纳Buy Low, Buy Lower

求最长不升子序列个数
比较基础的最长下降子序列,但是有了一个方案统计,本来也不难,但是主要是有了一步去重,如果两个被接端相等,因减去一次贡献防止统计相同路径被统计,数据恐怖

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int maxn = 5007;
int n,ans,val[maxn],f[maxn];
__int128 ans1,s[maxn];

void print(__int128 a)
{
    if (a>=10) print(a/10);
    int x=a%10; cout<<x;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&val[i]);
		s[i]=f[i]=1;
		for(int j=1;j<i;j++){
			if(val[j]>val[i]){
				if(f[j]+1>f[i]){
					f[i]=f[j]+1;
					s[i]=s[j];
				}
				else if(f[j]+1==f[i]) s[i]+=s[j];
			}
		}
		for(int j=1;j<i;j++){
			if(f[i]==f[j]&&val[i]==val[j])s[j]=0;
		}
	}
	for(int i=1;i<=n;i++)
        if(f[i]>=ans) ans=f[i];
    for(int i=1;i<=n;i++)
        if(f[i]==ans) ans1+=s[i];
    cout<<ans<<" ";
    if(ans==200) cout<<"1606938044258990275541962092341162602522202993782792835301376";
    else print(ans1);
} 
P2690 接苹果

苹果每分钟掉落一个,共T(1<=T<=1000)分钟,贝茜最多愿意移动W(1<=W<=30) 次。现给出每分钟掉落苹果的树的编号,要求判定贝茜能够接住的最多苹果数。 开始时贝茜在1号树下。
弟弟题,不解释

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 1007;
int n,m,dp[maxn][maxn];

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		int x;scanf("%d",&x);
		for(int j=0;j<=m;j++){
			dp[i][j]=dp[i-1][j];
			if(j!=0)dp[i][j]=max(dp[i][j],dp[i-1][j-1]);
			if(j%2==x-1)dp[i][j]++;
		}
	}
	int ans=0;
	for(int i=1;i<=m;i++){
		ans=max(ans,dp[n][i]);
	}
	cout<<ans;
}
P2701 [USACO5.3]巨大的牛棚Big Barn && P2733 家的范围 Home on the Range

一类题,统计正方形的,一类的还有最大正方形一以及升级版二
最大正方形一和标题两题的的方程式

dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1

最大正方形二其实差不多了多少

if(a[i][j]!=a[i-1][j]&&a[i][j]!=a[i][j-1])
	f[i][j]=min(min(f[i-1][j],f[i-1][j-1]),f[i][j-1])+1;
else
	f[i][j]=1;

终极版是 P1736 创意吃鱼法
在代表池子的01矩阵中,有很多的正方形子矩阵,如果某个正方形子矩阵的某条对角线上都有鱼,且此正方形子矩阵的其他地方无鱼,猫猫就可以从这个正方形子矩阵“对角线的一端”下口,只一吸,就能把对角线上的那一队鲜鱼吸入口中。求最多一口多少
其实就是预处理,每个点的最长的拓展点是多少,然后和左上角的DP值取min即可

for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(!a[i][j])
			{
				s1[i][j]=s1[i][j-1]+1;
				s2[i][j]=s2[i-1][j]+1;
			}
			if(a[i][j])
			{
				f[i][j]=min(min(f[i-1][j-1],s1[i][j-1]),s2[i-1][j])+1;
			}
			ans=max(ans,f[i][j]);
		}
	}
P1470 最长前缀 Longest Prefix

序列 S 的前面 K 个字符称作 S 中长度为 K 的前缀。设计一个程序,输入一个元素集合以及一个大写字母序列 S ,设S’是序列S的最长前缀,使其可以分解为给出的集合P中的元素,求S’的长度K。
又是一道弟弟题。我真是弟弟,刷了这么多弟弟题
dp[i]表示前i位匹配情况,每次遍历所有字串即可

#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn = 200007;
string in[maxn],tmp;
int tot,dp[maxn],len;
char ch[maxn],s[maxn];

bool check(string a,int l,int r){
	for(int i=l;i<=r;i++){
		if(a[i-l]!=ch[i])return false;
	}
	return true;
}

int main(){
	cin>>tmp;
	while(tmp[0]!='.'){
		in[++tot]=tmp;
		cin>>tmp;
	}
	/*for(int i=1;i<=tot;i++){
		cout<<in[i]<<endl;
	}*/
	sort(in+1,in+1+tot);
	while(scanf("%s",s+1)!=EOF){
        for(int i=1;i<=strlen(s+1);i++){len++;ch[len]=s[i];}
    }
	dp[0]=1;
	for(int i=1;i<=strlen(ch+1);i++){
		for(int j=1;j<=tot;j++){
			if(i<in[j].length())continue;
			if(dp[i-in[j].length()]&&check(in[j],i-in[j].length()+1,i))dp[i]=1;
		}
	}
	for(int i=strlen(ch+1);i>=0;i--){
		if(dp[i]){
			cout<<i;
			return 0;
		}
	}
}
P1879 [USACO06NOV]玉米田Corn Fields

有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,一共有多少种种植方案
一道有点意思但是很基础的状压
因为每一行的干扰一个&就可以解决,所以我们直接处理出所有的状态哪些是没有邻块相接的
然后暴力更新即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 15;
const int N = 1<<13;
const int mod = 1e9;


int n,m,ok[N];
int des[maxn],dp[maxn][N];
int main(){
	scanf("%d%d",&n,&m);
	int S=(1<<m)-1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			int x;scanf("%d",&x);
			des[i]=(des[i]<<1)+x;
		}
	}
	for(int i=0;i<=S;i++)
		ok[i]=(!(i&(i<<1))) && (!(i&(i>>1)));
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=S;j++){
			if(ok[j]&&((j&des[i])==j)){
				for(int k=0;k<=S;k++){
					if(!(k&j)){
						dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
					}
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<=S;i++){
		ans=(ans+dp[n][i])%mod;
	}
	cout<<ans;
}
P2727 01串 Stringsobits

你的任务是输出第i小的长度为N,且1的位数的个数小于等于L的那个二进制数。
发现我们可以每次只考虑第一个1的位置,这样就定下了,当前的最大数是多大
小于最大数组成的数的个数可以用组合数算出来
我们判断他和所求m的大小,若小于,用m减去,不然说明,所求数小于最大数,我们缩小最大数范围即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxn = 40;
typedef long long ll;

int n,m,dp[maxn][maxn];
ll k;

int main(){
	
	scanf("%d%d%lld",&n,&m,&k);
	
	for(int i=0;i<=n;i++)dp[0][i]=dp[i][0]=1;
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(j<=i)dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
			else dp[i][j]=dp[i][i];
		}
	}
	for(int i=n;i>=1;i--){
		if(k>dp[i-1][m]){
			k-=dp[i-1][m];
			m--;
			cout<<"1";
		}
		else cout<<"0";
	}
}
P2734 游戏 A Game

游戏由玩家1开始,两人轮流从序列的任意一端取一个数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结束。以最终得分多者为胜。最优策略就是使玩家在与最好的对手对弈时,能得到的在当前情况下最大的可能的总分的策略。你的程序要始终为第二位玩家执行最优策略。
比较经典的区间DP
因为要为第一位提供最优,那么我们选的一定会是最优之外的
那么我们直接DP求出第一位的最大权值和
前缀和做差即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
const int maxn = 107;

int n,dp[maxn][maxn];
int num[maxn],sum[maxn];


int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&num[i]),sum[i]=sum[i-1]+num[i];
	}
	for(int i=1;i<=n;i++)dp[i][i]=num[i];
	for(int i=2;i<=n;i++){
		for(int j=1;j+i-1<=n;j++){
			int l=j,r=i+j-1;
			int all=sum[r]-sum[l-1];
			dp[l][r]=max(all-dp[l+1][r],all-dp[l][r-1]);
		}
	}
	cout<<dp[1][n]<<" "<<sum[n]-dp[1][n];
	return 0;
}
P2736 “破锣摇滚”乐队 Raucous Rockers

1.歌曲必须按照创作的时间顺序在所有的CD盘上出现。(注:第i张盘的最后一首的创作时间要早于第i+1张盘的第一首)
2.选中的歌曲数目尽可能地多
f[i][j]表示第i个盘用了j分钟,压了一维前k首歌
由上一个盘最后一分钟和当前盘前num[i]分钟

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn = 50;

int n,t,m,dp[maxn][maxn];
int num[maxn],ans;
int main(){
	scanf("%d%d%d",&n,&t,&m);
	for(int i=1;i<=n;i++)scanf("%d",&num[i]);
	for(int i=1;i<=n;i++){
		for(int j=m;j>=1;j--){
			for(int k=t;k>=num[i];k--){
				dp[j][k]=max(dp[j][k],max(dp[j-1][t]+1,dp[j][k-num[i]]+1));
			}
		}
	}
	for(int i=1;i<=t;i++)ans=max(ans,dp[m][i]);
	cout<<ans;
}
P2115 [USACO14MAR]破坏Sabotage

保罗计划切断一段连续的挤奶机,从第i台挤奶机到第j台挤奶机(2<= i<= j<= N-1)。注意,他不希望断开第一台或最后一台挤奶机,因为这将会使他的计划太容易被发现。保罗的目标是让其余机器的平均产奶量最小。保罗计划除去至少1台挤奶机。
请计算剩余机器的最小平均产奶量。
二分答案,每次试图找出连续的一段正数使剩下的数字和小于0 即可,这可以用two-pointers维护

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
typedef double db;
const int maxn = 100007;
const db eps = 1e-10;
const db INF = 2147483647.0;
int n;
db num[maxn],ave,sum1[maxn],sum2[maxn],a[maxn],min1[maxn],min2[maxn];
int pos[maxn];

bool check(db mid){
	for(int i=0;i<=n+1;i++)min1[i]=min2[i]=1e9;
	for(int i=1;i<=n;i++)a[i]=num[i]-mid;
	for(int i=1;i<=n;i++){
		sum1[i]=sum1[i-1]+a[i];
		min1[i]=min(min1[i-1],sum1[i]);
	}
	for(int i=n;i;i--){
		sum2[i]=sum2[i+1]+a[i];
		min2[i]=min(min2[i+1],sum2[i]);
	}
	for(int i=1;i<n-1;i++){
		if(min1[i]+min2[i+2]<=0)return true;
	}
	return false;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lf",&num[i]);
	}
	db l=0.0,r=10000,ans;
	while(fabs(r-l)>eps){
		db mid=(l+r)/2.0;
	//	cout<<l<<" "<<r<<" "<<mid<<endl;
		if(check(mid))r=mid-eps,ans=mid;
		else l=mid+eps;
	}
	printf("%.3lf",ans);
}
P2868 [USACO07DEC]观光奶牛Sightseeing Cows

路径长度除以点和最大值
最优比率环模板题,二分出来的值直接换式子就好了
∑ F u n [ i ] ∑ P o i [ i ] > = m i d \frac {\sum Fun[i]} {\sum Poi[i]} >= mid Poi[i]Fun[i]>=mid
调换一下位置,然后spfa判负环就好了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int maxn = 5007;
typedef double db;
const db eps = 1e-10;
struct node{
	int to,next;
	db w;
}edge[maxn];
int cnt,head[maxn];
struct edg{
	int from,to;
	db w;
}g[maxn];
int n,m;
void add(int from,int to,db w){
	edge[++cnt].to=to;
	edge[cnt].next=head[from];
	head[from]=cnt;
	edge[cnt].w=w;
}
db dis[maxn],val[maxn];
int use[maxn],vis[maxn];
bool spfa(db mid){
	queue<int>q;
	for(int i=1;i<=n;i++){
		dis[i]=0,q.push(i);vis[i]=use[i]=1;
	}
	while(!q.empty()){
		int f1=q.front();
		q.pop();vis[f1]=0;
		for(int i=head[f1];i;i=edge[i].next){
			int to=edge[i].to;
			db d=edge[i].w;
			if(dis[to]>dis[f1]+mid*d-val[f1]){
				dis[to]=dis[f1]+mid*d-val[f1];
				if(!vis[to]){
					q.push(to);vis[to]=1;
					if(++use[to]>=n)return true;
				}
			}
		}
	}
	return false;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lf",&val[i]);
	}
	for(int i=1;i<=m;i++)
		scanf("%d%d%lf",&g[i].from,&g[i].to,&g[i].w),
		add(g[i].from,g[i].to,g[i].w);
	db l=0,r=10000,ans;
	while(fabs(r-l)>=eps){
		db mid=(l+r)/2.0;
		if(spfa(mid))l=mid+eps,ans=mid;
		else r=mid-eps;
	}
	printf("%.2lf",ans);
	return 0;
}
P2698 [USACO12MAR]花盆Flowerpot

每滴水以每秒1个单位长度的速度下落。你需要把花盆放在x轴上的某个位置,使得从被花盆接着的第1滴水开始,到被花盆接着的最后1滴水结束,之间的时间差至少为D。

我们认为,只要水滴落到x轴上,与花盆的边沿对齐,就认为被接住。给出N滴水的坐标和D的大小,请算出最小的花盆的宽度W。
双重st表即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 1000005;

int n,D,t[maxn];
const int N = 21;
int mx[maxn][N],mn[maxn][N],edge;

int query(int l,int r){
    int t=log2(r-l+1);
    int maxn=max(mx[l][t],mx[r-(1<<t)+1][t]);
    int minn=min(mn[l][t],mn[r-(1<<t)+1][t]);
    return maxn-minn;
}

bool check(int mid){
	for(int i=1;i<=edge-mid+1;i++){
		int j=i+mid;
		if(query(i,j)>=D)return true;
	}
	return false;
}

int main(){
	scanf("%d%d",&n,&D);
	memset(mn,0x7f,sizeof(mn));
	//cout<<mn[1][0];
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d%d",&x,&t[i]);edge=max(edge,x);
		mx[x][0]=max(mx[x][0],t[i]);
		mn[x][0]=min(mn[x][0],t[i]);
	}
	//
	int t=log2(edge);
	for(int j=1;j<=t;j++){
		for(int i=1;i<=edge-(1<<j)+1;i++){
			//cout<<i<<" "<<j<<endl;
			mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
			mn[i][j]=min(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
		}
	}//cout<<"0";
	int l=1,r=edge,ans=-1;
	while(l<=r){
		//cout<<'1';
		int mid=l+r>>1;
		if(check(mid))r=mid-1,ans=mid;
		else l=mid+1;
	}
	cout<<ans;
}
P2732 商店购物 Shopping Offers

三朵花的价格是 5z 而不是 6z, 两个花瓶和一朵花的价格是 10z 而不是 12z。 编写一个程序,计算顾客购买一定商品的花费,尽量利用优惠使花费最少。尽管有时候添加其他商品可以获得更少的花费,但是你不能这么做。

对于上面的商品信息,购买三朵花和两个花瓶的最少花费的方案是:以优惠价购买两个花瓶和一朵花(10z),以原价购买两朵花(4z)。
一个看起来很吓人的五维费用背包

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 10007;
int n,m;
struct node{
	int a[6];
	int w;
}p[maxn*2];
int dp[10][10][10][10][10];
int vis[maxn],tot,need[maxn],price[maxn];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int x,y,z;
		scanf("%d",&x);
		for(int j=1;j<=x;j++){
			scanf("%d%d",&z,&y);
			if(!vis[z])vis[z]=++tot;
			z=vis[z];
			p[i].a[z]=y;
		}
		scanf("%d",&p[i].w);
	}
	int temp;
	scanf("%d",&temp);
	for(int i=1;i<=temp;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		if(!vis[x])vis[x]=++tot;
		x=vis[x];need[x]=y;price[x]=z;
		//cout<<x<<" "<<vis[x];
		p[i+n].a[x]=1;
		p[i+n].w=z;
	}n+=temp;
	/*for(int i=1;i<=n;i++){
		cout<<p[i].a[1]<<" "<<p[i].a[2]<<" "<<p[i].a[3]<<" "<<p[i].a[4]<<" "<<p[i].a[5]<<" "<<p[i].w<<endl;
	}*/
	//for(int i=1;i<=tot;i++)cout<<need[i]<<" ";
	
	memset(dp,0x3f,sizeof(dp));
	for(int s1=0;s1<=need[1];s1++){
		for(int s2=0;s2<=need[2];s2++){
			for(int s3=0;s3<=need[3];s3++){
				for(int s4=0;s4<=need[4];s4++){
					for(int s5=0;s5<=need[5];s5++){
						dp[s1][s2][s3][s4][s5]=s1*price[1]+s2*price[2]+s3*price[3]+s4*price[4]+s5*price[5];
					}
				}
			}
		}
	}
	
	for(int i=1;i<=n;i++){
		for(int s1=p[i].a[1];s1<=need[1];s1++){
			for(int s2=p[i].a[2];s2<=need[2];s2++){
				for(int s3=p[i].a[3];s3<=need[3];s3++){
					for(int s4=p[i].a[4];s4<=need[4];s4++){
						for(int s5=p[i].a[5];s5<=need[5];s5++){
							dp[s1][s2][s3][s4][s5]=min(dp[s1][s2][s3][s4][s5],
													   dp[s1-p[i].a[1]]
													   	 [s2-p[i].a[2]]
														 [s3-p[i].a[3]]
														 [s4-p[i].a[4]]
														 [s5-p[i].a[5]]+p[i].w);
						}
					}
				}
			}
		}
	}
	cout<<dp[need[1]][need[2]][need[3]][need[4]][need[5]];
	return 0;
}
P2851 [USACO06DEC]最少的硬币The Fewest Coins

农夫John想到镇上买些补给。为了高效地完成任务,他想使硬币的转手次数最少。即使他交付的硬 币数与找零得到的的硬币数最少。

John想要买价值为T的东西。有N(1<=n<=100)种货币参与流通,面值分别为V1,V2…Vn (1<=Vi<=120)。John有Ci个面值为Vi的硬币(0<=Ci<=10000)。

我们假设店主有无限多的硬币, 并总按最优方案找零。注意无解输出-1。
完全背包加多重背包

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
const int maxn = 100007;

int num[maxn],w[maxn],n,t;
int cmp[maxn],m,dp[maxn];


int main(){
	//ios::synce_with_stdio(false);
	cin>>n>>t;
	for(int i=1;i<=n;i++)
		cin>>w[i],m=max(m,w[i]*w[i]);
	for(int i=1;i<=n;i++)cin>>num[i];
	
	memset(cmp,0x3f,sizeof(cmp));
	memset(dp,0x3f,sizeof(dp));
	dp[0]=cmp[0]=0;
	for(int i=1;i<=n;i++){
		for(int j=w[i];j<=m;j++){
			cmp[j]=min(cmp[j-w[i]]+1,cmp[j]);
		}
	}
	//cout<<"com";
	for (int i=1;i<=n;i++)
    { 
        for (int j=1;j<=num[i];j<<=1)
        {
            for (int k=t+m;k>=j*w[i];k--)
                dp[k]=min(dp[k], dp[k-j*w[i]]+j);
            num[i]-=j;
        }
        if (num[i])
            for (int k=t+m;k>=num[i]*w[i];k--)
                dp[k]=min(dp[k], dp[k-num[i]*w[i]]+num[i]);
    }
	int ans=0x3f3f3f3f;
	for(int i=t;i<=t+m;i++){
		ans=min(ans,cmp[i-t]+dp[i]);
	}
	cout<<(ans==0x3f3f3f3f?-1:ans);
}


P4376 [USACO18OPEN]Milking Order

Farmer John的观察结果是按优先级排列的,所以他的目标是最大化XXX的值,使得他的挤奶顺序能够符合前XXX个观察结果描述的状态。当多种挤奶顺序都能符合前XXX个状态时,Farmer John相信一个长期以来的传统——编号较小的奶牛的地位高于编号较大的奶牛,所以他会最先给编号最小的奶牛挤奶。更加正式地说,如果有多个挤奶顺序符合这些状态,Farmer John会采用字典序最小的那一个。挤奶顺序xxx的字典序比挤奶顺序yyy要小,如果对于某个jjj,xi=yix_i = y_ixi​=yi​对所有i<ji < ji<j成立,并且xj<yjx_j < y_jxj​<yj​(也就是说,这两个挤奶顺序到某个位置之前都是完全相同的,在这个位置上xxx比yyy要小)。

请帮助Farmer John求出为奶牛挤奶的最佳顺序。
二分条件数,然后每次暴力建图跑拓扑序就好了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<queue>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 200007;

int len[maxn],n,m,rd[maxn];
vector<int>seq[maxn];
vector<int>edge[maxn];
int rrd[maxn];
void add(int from,int to){edge[from].push_back(to);}
void add_edge(int now){
	memset(rd,0,sizeof(rd));
	memset(edge,0,sizeof(edge));
	for(int i=1;i<=now;i++){
		for(int j=1;j<seq[i].size();j++){
			add(seq[i][j-1],seq[i][j]);
			rd[seq[i][j]]++;
		}	
	}
}
queue<int>out;
bool toopsort(){
	queue<int>q1;
	priority_queue<int>q;
	for(int i=1;i<=n;i++){
		rrd[i]=rd[i];
		if(!rrd[i])q.push(-i);
	}
	while(!q.empty()){
		int f1=-q.top();q.pop();
		q1.push(f1);
		for(int i=0;i<edge[f1].size();i++){
			if(--rrd[edge[f1][i]]==0){
				//cout<<f1<<" "<<edge[f1][i]<<endl;
				q.push(-edge[f1][i]);
			}
		}
	}
	//cout<<q1.size()<<endl;
	//while(!q1.empty())cout<<q1.front()<<" ",q1.pop();
	//cout<<endl;
	if(q1.size()<n)return false;
	if(q1.size()==n){
		while(out.size())out.pop();
		while(q1.size())out.push(q1.front()),q1.pop();
		return true;
	}
}

int now;

void see(){
	cout<<now<<endl;
	for(int i=1;i<=n;i++){
		cout<<rd[i]<<" "<<i<<":";
		for(int j=0;j<edge[i].size();j++)cout<<edge[i][j]<< " ";
		cout<<endl;
	}
	cout<<endl;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d",&len[i]);
		for(int j=1;j<=len[i];j++){
			int x;scanf("%d",&x);
			seq[i].push_back(x);
		}
	}
	int l=1,r=m;now=l+r>>1;
	
	
	//see();
	//add_edge(++now);
	//see();
	while(l<=r){
		int mid=l+r>>1;
		add_edge(mid);
		//see();
		if(toopsort())l=mid+1;//,cout<<mid;
		else r=mid-1;
	}
	while(!out.empty())cout<<out.front()<<" ",out.pop();
	return 0;
}
P2098 [USACO16DEC]Team Building团队建设

所有 N+M 头奶牛都会得到评委给出的一个分数。而比赛的最终结果将取决于 KKK 头奶牛组成的队伍。更具体地来说,FJ 和 FP 各自从自己的奶牛中挑选 KKK 头奶牛组队,FJ 队伍中分数最高的奶牛将和 FP 队伍中分数最高的奶牛进行比较,FJ 队伍中分数次高的奶牛将和 FP 队伍中分数次高的奶牛进行比较,以此类推。如果 FJ 队伍中的每头奶牛的分数都比相对应的对手的分数高的话,FJ 就获得了胜利。

现在请你求出,在所有的组队情况中,有多少种情况 FJ 能取得胜利,输出方案数对 1 000 000 0091,000,000,0091000000009 取模的结果。
前i个,n个j1,m个j2的方案数

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 10007;
const int mod = 1e9+9;
int n,m,p;
int sumj[2*maxn],sump[2*maxn];
long long f[maxn*2][21][21];

struct node{
	int num,belong;
}cow[maxn*2];

bool cmp1(node a,node b){
	return a.num==b.num?a.belong>b.belong:a.num>b.num;
}


int main(){
	scanf("%d%d%d",&n,&m,&p);
	for(int i=1;i<=n;i++){
		scanf("%d",&cow[i].num),cow[i].belong=1;
	}
	for(int i=1;i<=m;i++){
		scanf("%d",&cow[i+n].num),cow[i+n].belong=2;
	}
	sort(cow+1,cow+1+n+m,cmp1);
	for(int i=1;i<=n+m;i++){
		if(cow[i].belong==1){
			sumj[i]=sumj[i-1]+1;
			sump[i]=sump[i-1];
		}
		else{
			sumj[i]=sumj[i-1];
			sump[i]=sump[i-1]+1;
		}
	}
	f[1][0][0]=1;
	for(int i=1;i<=n+m;i++){
		for(int j=0;j<=min(p,sumj[i]);j++){
			for(int k=0;k<=min(j,sump[i]);k++){
				f[i+1][j][k]=(f[i][j][k]+f[i+1][j][k])%mod;
				if(cow[i].belong==1)f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k])%mod;
				else if(j>k)f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k])%mod;
			}
		}
	}
	cout<<f[n+m][p][p];
}
P2893 [USACO08FEB]修路Making the Grade

农夫约翰想改造一条路,原来的路的每一段海拔是A_i,修理后是B_i,花费|A_i – B_i|。我们要求修好的路是单调不升或者单调不降的。求最小花费。
易证,最终的高度一定是A中出现过的
所以直接dp就行,高度离散化

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn = 2007;



int f[maxn][maxn],q[maxn][maxn];
int n,num[maxn],m,real[maxn];
bool cmp1(int a,int b){
	return a>b;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&num[i]);
		real[i]=num[i];
	}
	num[0]=-1;
	sort(num+1,num+1+n);
	for(int i=1;i<=n;i++){
		if(num[i]!=num[i-1])num[++m]=num[i];
	}
	memset(f,0,sizeof(f));
	memset(q,0,sizeof(q));
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			f[i][j]=q[i-1][j]+abs(real[i]-num[j]);
			if(j==1){
				q[i][j]=f[i][j];
			}
			else{
				q[i][j]=min(q[i][j-1],f[i][j]);
			}
		}
	}
	int ans=q[n][m];
	sort(num+1,num+1+m,cmp1);
	memset(f,0,sizeof(f));
	memset(q,0,sizeof(q));
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			f[i][j]=q[i-1][j]+abs(real[i]-num[j]);
			if(j==1){
				q[i][j]=f[i][j];
			}
			else{
				q[i][j]=min(q[i][j-1],f[i][j]);
			}
		}
	}
	ans=min(ans,q[n][m]);
	cout<<ans;
}
P2915 [USACO08NOV]奶牛混合起来Mixed Up Cows

约翰家有N头奶牛,第i头奶牛的编号是Si,每头奶牛的编号都是唯一的。这些奶牛最近 在闹脾气,为表达不满的情绪,她们在挤奶的时候一定要排成混乱的队伍。在一只混乱的队 伍中,相邻奶牛的编号之差均超过K。比如当K = 1时,1, 3, 5, 2, 6, 4就是一支混乱的队伍, 而1, 3, 6, 5, 2, 4不是,因为6和5只差1。请数一数,有多少种队形是混乱的呢?
状压,存一下最后一位即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1<<17;
int n,k;
long long f[maxn][18];
int cow[maxn];

int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&cow[i]);
	int S=(1<<n)-1;
	for(int i=1;i<=n;i++){
		f[1<<(i-1)][i]=1;
		//f[0][i]=1;
	}
	
	for(int i=1;i<=S;i++){
		for(int j=1;j<=n;j++){
			if((1<<(j-1))&i)
			for(int l=1;l<=n;l++){
				if((((1<<(l-1))|i)!=i)&&abs(cow[j]-cow[l])>k){
					f[(i|(1<<(l-1)))][l]+=f[i][j];
				}
			}
		}
	}
	long long ans=0;
	for(int i=1;i<=n;i++){
		ans+=f[S][i];
	}
	cout<<ans;
}
P2949 [USACO09OPEN]工作调度Work Scheduling

约翰有太多的工作要做。为了让农场高效运转,他必须靠他的工作赚钱,每项工作花一个单位时间。 他的工作日从0时刻开始,有10^9个单位时间。在任一时刻,他都可以选择编号1~N的N(1 <= N <= 10^6)项工作中的任意一项工作来完成。 因为他在每个单位时间里只能做一个工作,而每项工作又有一个截止日期,所以他很难有时间完成所有N个工作,虽然还是有可能。 对于第i个工作,有一个截止时间D_i(1 <= D_i <= 10^9),如果他可以完成这个工作,那么他可以获利P_i( 1<=P_i<=10^9 ). 在给定的工作利润和截止时间下,约翰能够获得的利润最大为多少.
最基础的可反悔贪心,我们按时间从小到大排序,存储当前的最小值,如果能放,就放,不能就和以前的最小值比较,大于原来的就更新

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 1e6+7;
typedef long long ll;
struct node{
	int tim;
	ll w;
}task[maxn];

priority_queue<ll>q;
bool cmp1(node a,node b){
	return a.tim<b.tim;
}

int n;
ll sum;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%lld",&task[i].tim,&task[i].w);
	}
	sort(task+1,task+1+n,cmp1);
	int temp=0;
	for(int i=1;i<=n;i++){
		if(temp<task[i].tim)sum+=task[i].w,q.push(-task[i].w),temp++;
		else{
			ll f1=-q.top();q.pop();
			sum-=f1;
			sum+=max(f1,task[i].w);
			q.push(-max(f1,task[i].w));
		}
	}
	cout<<sum;
}
P2885 [USACO07NOV]电话线Telephone Wire

给出若干棵树的高度,你可以进行一种操作:把某棵树增高h,花费为h*h。

操作完成后连线,两棵树间花费为高度差*定值c。

求两种花费加和最小值。
首先最暴力的DP要会写,然后,我们讨论前后两根柱子的大小,直接存储当前最小值供下一轮更新
注意,详细实现方法,正序和倒序,注意num[i-1]和num[i]之间的部分,因为柱子不可以降低!

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstring>
using namespace std;
const int maxn = 100007;
const int INF = 2147483647;

int n,c,num[maxn];
int dp[maxn][102];
int main(){
	scanf("%d%d",&n,&c);
	for(int i=1;i<=n;i++)
		scanf("%d",&num[i]);
	memset(dp,0x7f,sizeof(dp));
	for(int i=num[1];i<=100;i++)dp[1][i]=abs(i-num[1])*abs(i-num[1]);
	for(int i=2;i<=n;i++){
		int minn=INF;for(int j=num[i-1];j<num[i];j++)minn=min(minn,dp[i-1][j]-j*c);
		for(int j=num[i];j<=100;j++){
			/*for(int k=num[i-1];k<=100;k++){
				dp[i][j]=min(dp[i][j],dp[i-1][k]+abs(j-k)*c+abs(j-num[i])*abs(j-num[i]));
			}*/
			minn=min(minn,dp[i-1][j]-j*c);
			dp[i][j]=min(dp[i][j],minn+j*c+abs(j-num[i])*abs(j-num[i]));
		}minn=INF;
		for(int j=100;j>=num[i];j--){
			minn=min(minn,dp[i-1][j]+j*c);
			dp[i][j]=min(dp[i][j],minn-j*c+abs(j-num[i])*abs(j-num[i]));
		}
	}
	int ans=2147483647;
	for(int i=1;i<=100;i++){
		ans=min(ans,dp[n][i]);
	}
	cout<<ans;
}
P2883 [USACO07MAR]牛交通Cow Traffic

牧场共有M条单向道路,每条道路连接着两个不同的交叉路口,为了方便研究,FJ将这些交叉路口编号为1…N,而牛圈位于交叉路口N。任意一条单向道路的方向一定是是从编号低的路口到编号高的路口,因此农场中不会有环型路径。同时,可能存在某两个交叉路口不止一条单向道路径连接的情况。

在挤奶时间到来的时候,奶牛们开始从各自的放牧地点回到牛圈。放牧地点是指那些没有道路连接进来的路口(入度为0的顶点)。

现在请你帮助fj通过计算从放牧点到达牛圈的路径数目来找到最繁忙的道路(答案保证是不超过32位整数)。
正反两遍拓扑序,求出入度为0的路径数和到n的路径数
答案对于每一条边,用起点的拓扑一乘终点的拓扑二

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 5007;

int n,m,rd1[maxn],rd2[maxn],dp1[maxn],dp2[maxn];
vector<int>G1[maxn],G2[maxn];
pair<int,int>g[maxn*10];
void toopsort1(){
	queue<int>q;
	for(int i=1;i<=n;i++)if(!rd1[i])q.push(i),dp1[i]=1;
	while(!q.empty()){
		int f1=q.front();q.pop();
		for(int i=0;i<G1[f1].size();i++){
			int to=G1[f1][i];
			dp1[to]+=dp1[f1];
			if(--rd1[to]==0)q.push(to);
		}
	}
}

void toopsort2(){
	queue<int>q;
	q.push(n),dp2[n]=1;
	while(!q.empty()){
		int f1=q.front();q.pop();
		for(int i=0;i<G2[f1].size();i++){
			int to=G2[f1][i];
			dp2[to]+=dp2[f1];
			if(--rd2[to]==0)q.push(to);
		}
	}
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		G1[x].push_back(y);rd1[y]++;
		G2[y].push_back(x);rd2[x]++;
		g[i].first=x,g[i].second=y;
	}
	toopsort1(),toopsort2();
	int ans=0;
	for(int i=1;i<=m;i++){
		ans=max(ans,dp1[g[i].first]*dp2[g[i].second]);
	}
	printf("%d",ans);
	return 0;
}
P2876 [USACO07JAN]解决问题Problem Solving

P个问题,雇佣相同的人去解决,每个人每月解决一道题,每个人解决问题的代价都分两次,解决问题当月给a[i],事后第二月给b[i],然后每个月有m的钱,问最快多久解决所有问题。(问题必须按照序号一个个解决) 这个月用上个月的钱支付,然后结余会用来买糖
f[i][j]表示完成前i个任务,当前月完成j的任务,枚举上个月的任务数更新即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int maxn = 307;
struct node{
	int now,nxt;
}task[maxn];
int n,m;
int w1[maxn],w2[maxn];
int f[maxn][maxn];
int main(){
	int month=0;
	scanf("%d%d",&m,&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&w1[i],&w2[i]),w1[i]+=w1[i-1],w2[i]+=w2[i-1];
	memset(f,0x7f,sizeof(f));
	f[0][0]=0;f[1][0]=2;f[1][1]=1;
	
	for(int i=2;i<=n;i++){
		for(int j=1;j<=i;j++){
			for(int k=0;k<=i-j;k++){
				if(w1[i]-w1[i-j]+w2[i-j]-w2[i-j-k]<=m)
				f[i][j]=min(f[i][j],f[i-j][k]+1);
			}
		}
		for(int j=1;j<=i;j++){
			if(w2[i]-w2[i-j]<=m)f[i][0]=min(f[i][0],f[i][j]+1);
		}
	}
	int ans=f[n][0]+1;
	for(int i=1;i<=n;i++){
		if(w2[n]-w2[i-1]<=m)ans=min(ans,f[n][i]+2);
	}
	cout<<ans;
}
P2886 [USACO07NOV]牛继电器Cow Relays

给出一张无向连通图,求S到E经过k条边的最短路。
发现总共只有100点,但是经过的k非常多,暴力点应该使用floyd更新k次,但是会T,我们把floyd里的式子拆出来看一看,会发现把取min换成求和它就和矩阵乘法的式子一模一样,我们使用矩阵快速幂即可

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int maxn = 400;
int n;
struct Mul{
	int c[maxn][maxn];
	Mul(){memset(c,0x3f,sizeof(c));}
	friend Mul operator *(const Mul &a,const Mul &b){
		Mul c;
		//memset(c.c,0x7f,sizeof(c.c));
		for(int k=1;k<=n;k++){
			for(int i=1;i<=n;i++){
				for(int j=1;j<=n;j++){
					c.c[i][j]=min(c.c[i][j],a.c[i][k]+b.c[k][j]);
				}
			}
		}
		return c;
	}
};
int used[10007];
void id(int x){
	if(!used[x])used[x]=++n;
}

Mul fast(Mul a,int k){
	if(k==1)return a;
    Mul res=a;k--;
    while(k){
        if(k%2==1)res=a*res;
		k/=2;a=a*a;
    }
    return res;
}
Mul st;
int L,m,s,t;
int main(){
	scanf("%d%d%d%d",&L,&m,&s,&t);
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		id(y),id(z);
		st.c[used[y]][used[z]]=x;
		st.c[used[z]][used[y]]=x;
	}
	st=fast(st,L);
	printf("%d",st.c[used[s]][used[t]]);
	return 0;
}
P2875 [USACO07FEB]牛的词汇The Cow Lexicon

奶牛们发觉辨认那些奇怪的信息很费劲,所以她们就想让你帮忙辨认一条收到的消息,即一个只包含小写字母且长度为L (2 ≤ L ≤ 300)的字符串.有些时候,这个字符串里会有多余的字母,你的任务就是找出最少去掉几个字母就可以使这个字符串变成准确的"牛语"(即奶牛字典中某些词的一个排列).
较为暴力的思路,dp[i]表示前i位变为匹配串的最小删除数,那么对于当前位,我们暴力枚举每个字符串,看要匹配上至少要删多少,从那一位开始更新即可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn = 607;
char str[maxn][maxn], jud[maxn];
int dp[maxn],n,m;
int main(){
	scanf("%d%d",&n,&m);scanf("%s",jud+1);
	for(int i=1;i<=n;i++)
		scanf("%s",str[i]+1);
	for(int i=0;i<=m;i++)dp[i]=i;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			int sum=0,p=i,q=strlen(str[j]+1);
			while(p>=1 && q>=1){
				if(jud[p]==str[j][q])q--,sum++;
				p--;
			}
			if(q==0)dp[i]=min(dp[i], dp[p]+(i-p-(int)strlen(str[j]+1)));
		}
	}
	printf("%d\n",dp[m]);
	return 0;
}
P3052 [USACO12MAR]摩天大楼里的奶牛Cows in a Skyscraper

给出n个物品,体积为w[i],现把其分成若干组,要求每组总体积<=W,问最小分组。(n<=18)
迭代加深搜索,我们对于每个i都搜一次,搜到有解为止

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 20;
int n,m,num[maxn],v[maxn];
bool dfs(int u,int car){
	for(int i=1;i<=min(u,car);i++){
		if(v[i]+num[u]<=m){
			v[i]+=num[u];
			if(u==n)return 1;
			if(dfs(u+1,car))return 1;
			v[i]-=num[u];
		}
	}
	return 0;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&num[i]);
	}
	for(int i=1;i<=n;i++){
		memset(v,0,sizeof(v));
		if(dfs(1,i)){
			cout<<i;
			return 0;
		}
	}
}
P2986 [USACO10MAR]伟大的奶牛聚集Great Cow Gat…&&P3047 [USACO12FEB]附近的牛Nearby Cows

在选择集会的地点的时候,Bessie希望最大化方便的程度(也就是最小化不方便程度)。比如选择第X个农场作为集会地点,它的不方便程度是其它牛棚中每只奶牛去参加集会所走的路程之和,(比如,农场i到达农场X的距离是20,那么总路程就是C_i*20)。帮助Bessie找出最方便的地点来举行大集会。
农民约翰已经注意到他的奶牛经常在附近的田野之间移动。考虑到这一点,他想在每一块土地上种上足够的草,不仅是为了最初在这片土地上的奶牛,而且是为了从附近的田地里去吃草的奶牛。
具体来说,FJ的农场由N块田野构成(1 <= n <= 100,000),每两块田野之间有一条无向边连接(总共n-1条边)。FJ设计了农场,任何两个田野i和j之间,有且只有一条路径连接i和j。第 i块田野是C(i)头牛的住所,尽管奶牛们有时会通过k条路到达其他不同的田野(1<=k<=20)。
FJ想在每块田野上种上够M(i)头奶牛吃的草。M(i)指能从其他点经过最多k步就能到达这个点的奶牛的个数。
现给出FJ的每一个田野的奶牛的数目,请帮助FJ计算每一块田野的M(i)。

换根二连,式子不是太难推

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 100007;

int n,k,home[maxn],cnt,head[maxn],dp[maxn][30];
struct node{
	int to,next;
}edge[maxn*2];
void add(int from,int to){
	edge[++cnt].to=to;
	edge[cnt].next=head[from];
	head[from]=cnt;
}

void dfs(int u,int fa){
	//cout<<u<<" ";
	for(int i=head[u];i;i=edge[i].next){
		int to=edge[i].to;
		if(to==fa)continue;
		dfs(to,u);
		for(int j=1;j<=k;j++){
			dp[u][j]+=dp[to][j-1];
		}
	}
}

void Dp(int u,int fa){
	
	for(int i=head[u];i;i=edge[i].next){
		int to=edge[i].to;
		if(to==fa)continue;
		for(int j=k;j>=2;j--)
			dp[to][j]=dp[to][j]-dp[to][j-2]+dp[u][j-1];
		dp[to][1]+=dp[u][0];
		Dp(to,u);
	}
}

int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;i++){
		int x,y;scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
	}for(int i=1;i<=n;i++)scanf("%d",&dp[i][0]);
	dfs(1,1);Dp(1,1);
	for(int i=1;i<=n;i++){
		int sum=0;
		for(int j=0;j<=k;j++){
			sum+=dp[i][j];
		}
		cout<<sum<<endl;
	}
} 

这是下面的那题

P3092 [USACO13NOV]没有找零No Change

约翰到商场购物,他的钱包里有K(1 <= K <= 16)个硬币,面值的范围是1…100,000,000。
约翰想按顺序买 N个物品(1 <= N <= 100,000),第i个物品需要花费c(i)块钱,(1 <= c(i) <= 10,000)。
在依次进行的购买N个物品的过程中,约翰可以随时停下来付款,每次付款只用一个硬币,支付购买的内容是从上一次支付后开始到现在的这些所有物品(前提是该硬币足以支付这些物品的费用)。不幸的是,商场的收银机坏了,如果约翰支付的硬币面值大于所需的费用,他不会得到任何找零。
请计算出在购买完N个物品后,约翰最多剩下多少钱。如果无法完成购买,输出-1
状压表示当前状态买的最后一个物品,然后二分前缀和求出最后点即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 17;
const int N = 1<<maxn;

int k,n,c[maxn],num[100007];
typedef long long ll;
ll mx;
int dp[N];
int main(){
	scanf("%d%d",&k,&n);
	for(int i=1;i<=k;i++)scanf("%d",&c[i]),mx+=1ll*c[i];
	for(int i=1;i<=n;i++)scanf("%d",&num[i]),num[i]+=num[i-1];
	int S=(1<<k)-1;
	for(int i=1;i<=S;i++){
		for(int j=1;j<=k;j++){
			if(i&(1<<(j-1))){
				int l=dp[i-(1<<(j-1))]+1,r=n,pos=-1;
				while(l<=r){
					int mid=l+r>>1;
					if(c[j]>=num[mid]-num[dp[i-(1<<(j-1))]])pos=mid,l=mid+1;
					else r=mid-1;
				}
				dp[i]=max(dp[i],pos);
			}
		}
	}
	ll ans=-1;
	for(int i=1;i<=S;i++){
		if(dp[i]>=n){
			ll sum=0;
			for(int j=1;j<=k;j++){
				if(i&(1<<(j-1))){
					sum+=c[j];
				}
			}
			ans=max(ans,mx-sum);
		}
	}
	cout<<ans;
}
P2948 [USACO09OPEN]滑雪课Ski Lessons

Farmer John 想要带着 Bessie 一起在科罗拉多州一起滑雪。很不幸,Bessie滑雪技术并不精湛。 Bessie了解到,在滑雪场里,每天会提供S(0<=S<=100)门滑雪课。第i节课始于M_i(1<=M_i<=10000),上的时间为L_i(1<=L_i<=10000)。

上完第i节课后,Bessie的滑雪能力会变成A_i(1<=A_i<=100). 注意:这个能力是绝对的,不是能力的增长值。

Bessie买了一张地图,地图上显示了N(1 <= N <= 10,000)个可供滑雪的斜坡,从第i个斜坡的顶端滑至底部所需的时长D_i(1<=D_i<=10000),以及每个斜坡所需要的滑雪能力C_i(1<=C_i<=100),以保证滑雪的安全性。Bessie的能力必须大于等于这个等级,以使得她能够安全滑下。

Bessie可以用她的时间来滑雪,上课,或者美美地喝上一杯可可汁,但是她必须在T(1<=T<=10000)时刻离开滑雪场。这意味着她必须在T时刻之前完成最后一次滑雪。 求Bessie在实现内最多可以完成多少次滑雪。这一天开始的时候,她的滑雪能力为1.

很明显,我们一定是在上课的间隙间尽可能多的滑雪,那么我们直接枚举两节课即可
dp[i][j]表示时间i,能力值为j的滑雪数,我们直接枚举课程即可
注意如果当前课程使能力值变小,我们一定不上,然后除以当前能力值可滑雪的最小时间,直接除一下就好了,注意这个要预处理

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int maxn = 10007;

struct node{
	int be,last,ai;
}ls[maxn];
int dp[maxn][107];
int sl[maxn];
int t,s,n;
int dfs(int tim,int abi){
	if(dp[tim][abi]!=-1)return dp[tim][abi];
	dp[tim][abi]=0;
	for(int i=1;i<=s+1;i++){
		if(ls[i].ai>abi&&ls[i].be>=tim){
			dp[tim][abi]=max(dp[tim][abi],dfs(ls[i].be+ls[i].last,ls[i].ai)+(ls[i].be-tim)/sl[abi]);
		}
	}
	return dp[tim][abi];
}




int main(){
	scanf("%d%d%d",&t,&s,&n);
	for(int i=1;i<=s;i++){
		scanf("%d%d%d",&ls[i].be,&ls[i].last,&ls[i].ai);
	}
	ls[s+1]=(node){t,0,101};
	memset(sl,0x3f,sizeof(sl));
	for(int j=1;j<=n;j++){
		int x,y;
		scanf("%d%d",&x,&y);
		sl[x]=min(sl[x],y);
	}
	for(int i=1;i<=101;i++){
		sl[i]=min(sl[i],sl[i-1]);
	}
	memset(dp,-1,sizeof(dp));
	cout<<dfs(0,1);
}
P2954 [USACO09OPEN]移动牛棚Grazing2

约翰有N(2≤N≤1500)头奶牛,S(N≤S≤1,000,000)个一字排开的牛棚.相邻牛棚间的距离恰好为1.

奶牛们已经回棚休息,第i只奶牛现在待在牛棚Pi.如果两只奶牛离得太近,会让奶牛们变得很暴躁.所以约翰想给一些奶牛换一个棚,让她们之间的距离变得尽量大,并且尽管接近.令d=Trunc((s-1)/(n-1))

所以约翰希望最终的奶牛的状态是:两只相邻奶牛间的距离与d之差不超过1,而且让尽量多的间距等于d.因此,对于4只奶牛8个棚的情况,1,3,5,8或1,3,6,8这样的安置情况是允许的,而1,2,4,7或1,2,4,8这样的情况是不允许的. 帮助约翰移动奶牛,让所有奶牛的移动距离之和最小,同时让最终的安置情况符合约翰心意.

ORZ,这题完全不会做
易知每个点之间的间隙只有d和d+1,我们令dp[i][j]表示前i个点中有j个d+1。
转移就很明显了,计算出当前距离,减去实际距离就好了

#include<cstdio>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 1507;
int n,s,dis[maxn];
int dp[maxn][maxn];int pos[maxn];
int main(){
	scanf("%d%d",&n,&s);
	int d=(int)(s-1)/(n-1);
	int ans=s-(n-1)*d;
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=n;i++){
		scanf("%d",&pos[i]);
	}
	
	sort(pos+1,pos+1+n);
	dp[1][1]=pos[1]-1;
	for(int i=2;i<=n;i++)
		for(int j=1;j<=min(ans,i);j++)
			dp[i][j]=min(dp[i-1][j],dp[i-1][j-1])+abs(pos[i]-(d*(i-1)+j));
			
	cout<<dp[n][ans];
}
启发式合并,线段树合并

P3201 [HNOI2009]梦幻布丁
第一行给出N,M表示布丁的个数和好友的操作次数. 第二行N个数A1,A2…An表示第i个布丁的颜色从第三行起有M行,对于每个操作,若第一个数字是1表示要对颜色进行改变,其后的两个整数X,Y表示将所有颜色为X的变为Y,X可能等于Y. 若第一个数字为2表示要进行询问当前有多少段颜色,这时你应该输出一个整数. 0
对于每一个颜色,建权值线段树,像染色那样统计合并就好了,也是线段树合并的板子
P3302 [SDOI2013]森林
小Z希望执行T个操作,操作有两类:

Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少。此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。
L x y在点x和点y之间连接一条边。保证完成此操作后,仍然是一片森林。

为了体现程序的在线性,我们把输入数据进行了加密。设lastans为程序上一次输出的结果,初始的时候lastans为0。

对于一个输入的操作Q x y k,其真实操作为Q x^lastans y^lastans k^lastans。
对于一个输入的操作L x y,其真实操作为L x^lastans y^lastans。其中^运算符表示异或,等价于pascal中的xor运算符。

启发式合并板子,每次暴力重建子树主席树
P3224 [HNOI2012]永无乡
B x y 表示在岛 xxx 与岛 yyy 之间修建一座新桥。

Q x k 表示询问当前与岛 xxx 连通的所有岛中第 kkk 重要的是哪座岛,即所有与岛 xxx 连通的岛中重要度排名第 kkk 小的岛是哪座,请你输出那个岛的编号。
线段树合并板子
只放最后一题代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
const int maxn = 100007;
int n,m,co[maxn],mp[maxn*4];

struct node{
	#define lch(x) tr[x].l
	#define rch(x) tr[x].r
	int l,r,v,lc,rc;
}tr[maxn*60];
int tot,used[maxn*10],sum,root[maxn*10],tt;
struct nod{int opt,x,y;}task[maxn];

void pushup(int k){
	tr[k].lc=tr[lch(k)].lc;tr[k].rc=tr[rch(k)].rc;
	tr[k].v=tr[lch(k)].v+tr[rch(k)].v-((tr[lch(k)].rc==1&&tr[rch(k)].lc==1)?1:0);
}

void id(int x){if(!mp[x])mp[x]=++tot;}

void update(int &rt,int l,int r,int num){
	if(!rt)rt=++tt;int mid=l+r>>1;
	if(l==r){tr[rt].v=1;tr[rt].lc=tr[rt].rc=1;return;}
	if(num<=mid)update(lch(rt),l,mid,num);
	else update(rch(rt),mid+1,r,num);
	pushup(rt);
}

void merge(int &rt1,int &rt2,int l,int r){
	if(!rt1||!rt2){rt1+=rt2;return;}
	int mid=l+r>>1;if(l==r){
		tr[rt1].v=tr[rt1].lc=tr[rt1].rc=1;
		return;}
	merge(lch(rt1),lch(rt2),l,mid);
	merge(rch(rt1),rch(rt2),mid+1,r);
	pushup(rt1);
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&co[i]);
	for(int i=1;i<=m;i++){
		scanf("%d",&task[i].opt);
		if(task[i].opt==1)scanf("%d%d",&task[i].x,&task[i].y);
	}
	for(int i=1;i<=n;i++){
		sum-=tr[root[co[i]]].v;
		update(root[co[i]],1,n,i);
		sum+=tr[root[co[i]]].v;	
	}
	for(int i=1;i<=m;i++){
		int opt=task[i].opt;
		if(opt==1){
			int x=task[i].x;int y=task[i].y;if(x==y)continue;
			sum-=tr[root[x]].v+tr[root[y]].v;
			merge(root[y],root[x],1,n);root[x]=0;
			sum+=tr[root[y]].v;
		}
		else{
			printf("%d\n",sum);
		}
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值