牛客 施魔法
思路:
code:
//https://ac.nowcoder.com/acm/contest/3003/H
#include<bits/stdc++.h>
using namespace std;
const int maxm=3e5+5;
int a[maxm];
int d[maxm];//d[i]表示用掉去前i个元素的最小代价
//d[i]=min{d[j-1]+a[i]-a[j]}=min{d[j-1]-a[j]}+a[i] 1<=j<=(i-k+1)
signed main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
for(int i=1;i<k;i++)d[i]=2e9;
int pre=0-a[1];
for(int i=k;i<=n;i++){
d[i]=pre+a[i];
pre=min(pre,d[i-k+1]-a[i-k+2]);
}
cout<<d[n]<<endl;
return 0;
}
牛客 牛牛的宝可梦Go
题面:
思路:
code:
//https://ac.nowcoder.com/acm/contest/3004/J
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e3+5;
int g[maxm][maxm];
int d[maxm*100];
struct Node{
int t,p,v;
}e[maxm*100];
bool cmp(Node a,Node b){
return a.t<b.t;
}
signed main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)g[i][j]=(i==j?0:1e9);
}
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
g[a][b]=g[b][a]=1;
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}
}
}
int k;
cin>>k;
for(int i=1;i<=k;i++){
cin>>e[i].t>>e[i].p>>e[i].v;
}
e[0].p=1;
sort(e+1,e+1+k,cmp);
int premax=0;
int ans=0;
for(int i=1;i<=k;i++){
if(i>200){
premax=max(premax,d[i-200]);
d[i]=premax+e[i].v;
}else{
d[i]=-1e18;//不能初始化为0,否则之后的数可能会利用这个数转移
}
for(int j=1;j<=200&&i-j>=0;j++){
if(g[e[i-j].p][e[i].p]<=e[i].t-e[i-j].t){//如果时间差内可以到达则可以转移
d[i]=max(d[i],d[i-j]+e[i].v);
}
}
ans=max(ans,d[i]);
}
cout<<ans<<endl;
return 0;
}
Acwing 272. 最长公共上升子序列
题意:
给长度为n的两个数组a和b
问最长公共上升子序列的长度是多少。
最长公共上升子序列定义为:既是a的一个上升子序列,也是b的一个上升子序列
n<=3e3
思路:
f[i][j]表示前i个a[i]中,以b[j]为结尾的最大公共上升子序列
则对于一个固定的i和j,f[i][j]的值有两种情况:
1.不含a[i],则f[i][j]=f[i-1][j]
2.包含a[i],那么a[i]是结尾,又因为b[j]是结尾,因此需要先满足a[i]=b[j]
这时候f[i][j]=max(f[i-1][k])+1,(k<j,且a[i]>b[k])
枚举i一层循环,枚举j一层循环,枚举k一层循环,复杂度O(n^3),显然需要优化
f[i-1][k]用一个变量存起来,不断维护就可以优化掉枚举k了
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=3e3+5;
int a[maxm],b[maxm];
int f[maxm][maxm];
//f[i][j]表示前i个a[i]中以b[j]为结尾的最大长度
signed main(){
int n=re;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
for(int i=1;i<=n;i++){
int ma=0;//记录f[i-1][k]中最大的
for(int j=1;j<=n;j++){
f[i][j]=f[i-1][j];//不含a[i]
if(a[i]==b[j]){//包含a[i],则结尾为a[i],又因为结尾为b[j],则要满足a[i]=b[j]
f[i][j]=max(f[i][j],ma+1);//f[i-1][k]中最大的+1,(k<j)
}
if(a[i]>b[j]){//更新ma
ma=max(ma,f[i-1][j]);
}
}
}
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,f[n][i]);
}
cout<<ans<<endl;
return 0;
}
CodeForces 1096 D. Easy Problem
题意:
给定一个长度为n的字符串s,只由字符hard组成
删除s(i)的代价为a(i)
问使得串中不存在子序列hard的最小代价
思路:
开始想的是删除某一种字符就行了,这样是不对的
反例:
5
harard
100 1 100 100 1 100
正解:
因为子序列是存在顺序的问题,所以考虑维护使其不存在每个前缀的最小代价
1代表h
2代表ha
3代表har
4代表hard
d(i,sta)表示前i个字符中令其无法构成子序列sta的最小代价
对于无法构成前i个前缀的最小花费,先考虑无法构成前i-1个字符的最小花费 然后再考虑将这个位置的字符删除
ps:
由于只需要前一个位置的dp值
所以还可以顺便滚动数组优化
别人这题似乎被归到字符串dp了(原来dp还有单独出字符串一类?!)
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e5+5;
char s[maxm];
int a[maxm];
int d[5];//滚动数组
signed main(){
int n;
cin>>n;
scanf("%s",s+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
if(s[i]=='h'){
d[1]=d[1]+a[i];
}else if(s[i]=='a'){
d[2]=min(d[1],d[2]+a[i]);
}else if(s[i]=='r'){
d[3]=min(d[2],d[3]+a[i]);
}else if(s[i]=='d'){
d[4]=min(d[3],d[4]+a[i]);
}
}
cout<<d[4]<<endl;
return 0;
}
CodeForces687 C. The Values You Can Make
题意:
给定n枚银币,每个硬币有价值c(i),现在给定一个k,保证n枚硬币一定能构成k
问构成k的这些硬币还能构成多少其他的数,输出这些数。
例如用1,4,5,5构成10,
一种方案是1,4,5,其中1,4,5还可以构成0,1,4,5,6,9,10
一种方案是5,5,其中5,5还可以构成0,5,10
答案就是他们的并集0,1,4,5,6,9,10
思路:
d(i,j)表示凑成i元的时候是否能用凑成i元的硬币构成j元,如果可以,则d(i,j)=1,否则为0,显然j<=i
对于新加入的硬币x,如果d(i,j)=1,那么d(i+x,j)=1,d(i+x,j+x)=1,转移方程就出来了
因此对于每一枚硬币x,枚举i 和 j,进行转移即可。
因为d(i,j)会更新d(i+x,j)和d(i+x,j+x),为了保证无后效性,i和j都需要逆序枚举。
最后统计满足d(k,i)=1的i有多少个即可。
初始情况下d(0,0)=1
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=505;
int d[maxm][maxm];
int n,k;
signed main(){
cin>>n>>k;
d[0][0]=1;
while(n--){
int x;
cin>>x;
for(int i=k;i>=0;i--){//逆序
if(i+x<=k){
for(int j=i;j>=0;j--){//逆序
d[i+x][j]|=d[i][j];
d[i+x][j+x]|=d[i][j];
}
}
}
}
vector<int>ans;
for(int i=0;i<=k;i++){
if(d[k][i]){
ans.push_back(i);
}
}
cout<<ans.size()<<endl;
for(int v:ans){
cout<<v<<' ';
}
return 0;
}
CodeForces1101 D. GCD Counting
题意:
给一颗n个顶点的树,每个点有点权a(i),现在你要找出树上最长的路径,满足路径上的点权gcd不为1
输出这条路径的长度,路径的长度定义为路径上点的数量。
数据范围:n<=2e5,a(i)<=2e5
思路:
题目说gcd不为1,那么gcd只要是某个质因子就行。
发现2、3、5、7、9、11、13这7个质数的乘积以及大于2e5,因此每个数的质因子个数不超过7个。
对于树上每个点,只需要记录以这个点的每个质因子为gcd的最长向下扩展长度。
令d(i,j)表示编号为i的点,第j个质因子为gcd的最长扩展长度。
流程:
假设当前节点为x,遍历x的子节点,设子节点为v,
遍历x的质因子,假设第i个质因子为t,如果a(v)%t==0,说明v中也有质因子t,那么x可以向v扩展,
找到t在v的质因子的位置pos,(位置可以提前用map标记)
则d(v,pos)可能可以更新d(x,i),因为题目要求最长,所以d(x,i)要用最大的d(v,pos)更新,
记录位置i所能扩展的最大值和次大值(次大值用于计算答案,下面有说)
因为题目要求最大值,考虑把每个节点作为连接点,
以x为连接点的答案为1.x可扩展的最大值,2.x可扩展的次大值,3.因为还要算上x,因此+1
感觉看代码更好理解
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e5+5;
vector<int>g[maxm];
vector<int>fac[maxm];
map<int,int>mark[maxm];//mark(x,t)=pos表示t是x的第pos个质因子
int d[maxm][10];
int a[maxm];
int ans;
int n;
void dfs(int x,int fa){
for(int i=0;i<(int)fac[x].size();i++){
d[x][i]=1;
}
int ma[10]={0};//最大
int se[10]={0};//次大
for(int v:g[x]){
if(v==fa)continue;
dfs(v,x);
for(int i=0;i<(int)fac[x].size();i++){
int t=fac[x][i];
if(a[v]%t==0){
int pos=mark[v][t];
int x=d[v][pos];
if(x>ma[i]){
se[i]=ma[i];
ma[i]=x;
}else if(x>se[i]){
se[i]=x;
}
}
}
}
for(int i=0;i<fac[x].size();i++){
d[x][i]+=ma[i];
ans=max(ans,ma[i]+se[i]+1);
}
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
int x=a[i];
for(int j=2;j*j<=x;j++){
if(x%j==0){
fac[i].push_back(j);
while(x%j==0)x/=j;
}
}
if(x!=1)fac[i].push_back(x);
for(int j=0;j<(int)fac[i].size();j++){
mark[i][fac[i][j]]=j;//标记一下质因子的位置
}
}
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
dfs(1,-1);
cout<<ans<<endl;
return 0;
}
牛客 操作集锦
题意:
给一个长度为n的字符串,和一个整数k,问有多少个长度为k的本质不同的子序列
答案模1e9+7
思路:
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e3+5;
const int mod=1e9+7;
int d[maxm][maxm];//d[i][j]为前i个字符组成长度为j的子序列个数
int pre[maxm];
char s[maxm];
int n,k;
signed main(){
cin>>n>>k;
scanf("%s",s+1);
for(int i=0;i<=n;i++){//边界
d[i][0]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=k&&j<=i;j++){
d[i][j]+=d[i-1][j-1];//以i为结尾
if(j<=i-1){//不以i为结尾
d[i][j]+=d[i-1][j];
}
if(pre[s[i]-'a']&&j-1<=pre[s[i]-'a']-1){
d[i][j]-=d[pre[s[i]-'a']-1][j-1];
}
d[i][j]=(d[i][j]+mod)%mod;
}
pre[s[i]-'a']=i;
}
cout<<d[n][k]<<endl;
return 0;
}
//https://ac.nowcoder.com/acm/contest/4853/C
CodeForces269 B. Greenhouse Effect
题意:
有n株植物共m种,每株植物有种类和所在的坐标(坐标为实数),
现在要求将某些植物移动,使得相同种类的植物放在同一组(相邻),
并且第i种放在左边起第i组。
解法:
这题的坐标是没用的
最后的结果,植物的种类一定是非递减的
考虑有多少个植物最后不会移动,那么答案就是n-不移动的植物数量
不移动的植物数量为初始情况下植物种类的最长非递减序列,dp一下就行了
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=5e3+5;
int d[maxm];
int a[maxm];
signed main(){
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
double temp;cin>>temp;//坐标没用
}
int ans=0;
for(int i=1;i<=n;i++){
d[i]=1;
for(int j=1;j<i;j++){
if(a[i]>=a[j]){
d[i]=max(d[i],d[j]+1);
}
}
ans=max(ans,d[i]);
}
cout<<n-ans<<endl;
return 0;
}
hdu1421 搬寝室
题意:
由n个行李箱,需要从中拿出2k个行李箱,分k次拿,每次左手一个右手一个
假设某一次拿的行李箱的质量为a和b,那么这次的疲劳度就是(a-b)2
问k次的最小疲劳和是多少
数据范围:2k<=n<=2000
解法:
显然拿的行李箱的质量a和b的差值越小越好,因此先对行李箱按质量排序
定义d(i,j)为前i个行李箱取出j对行李项的最小疲劳值
如果某个物品i被取了,一定是和i-1匹配。
对于当前物品i,如果取则d(i,j)=d(i-2,j-1)+(a(i)-a(i-1)2
如果不取则d(i,j)=d(i-1,j)
复杂度O(n2)
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e3+5;
int d[maxm][maxm];
int a[maxm];
int n,k;
signed main(){
while(cin>>n>>k){
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)for(int j=1;j<=k;j++)d[i][j]=1e18;
//d[i][0]=0,不要初始化
for(int i=2;i<=n;i++){
for(int j=1;j<=k&&j*2<=i;j++){
d[i][j]=d[i-1][j];
d[i][j]=min(d[i][j],d[i-2][j-1]+(a[i]-a[i-1])*(a[i]-a[i-1]));
}
}
cout<<d[n][k]<<endl;
}
return 0;
}