DP和贪心的完美结合。
T1:n个州,你要从中选出K个进行演讲,每个州有键值(a,b),代表获得选票需要a时间,得到助理人需要b时间,a<=b。得到助理人之后可以同时演讲,演讲时间可以累加。问最少演讲时间。n<=500.
结论:
(1)假设选择若干个协助州,按照b排序的潜在协助州前缀一定不存在反对州
(2)如果要选择b=-1的州x个,一定按照a升序选前x小
(3)先选协助州,再选支持州
(4)m个人同时在一个州演讲,比分散不劣
所以设\(dp[i][j]表示前i个州选择j个协助州的最小时间(i<=min(K,helpstate))\)把州先按照b排序,-1都放后边,我们先枚举选择多少个协助州,假设b个,再进行dp,因为这样对于支持州我们可以直接算出它的花费,潜在协助州也可能成为支持州(a<<b)。求出dp[1K][b]后,我们枚举第一维位置,代表前1i个交杂支持和协助,剩下n-i个是全部支持,花费是a/(b+1)。预处理g[x][y]:x~n选协助州的前y小。\(O(K^2*n)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=0;
double f[510][510];
int g[510][510];
int n,K,bj;
struct node
{
int a,b;
}e[510],tmp[510];
bool cmp(node&A,node&B)
{
return A.a<B.a;
}
bool cmp2(node&A,node&B)
{
return A.b<B.b;
}
int main()
{
// freopen("1.in","r",stdin);
//freopen("a.out","w",stdout);
n=re(),K=re();
_f(i,1,n)
{
e[i].a=re(),e[i].b=re();
if(e[i].b==-1)e[i].b=1e9;
}
sort(e+1,e+1+n,cmp2);
int bj=-1;
_f(i,1,n)
if(bj==-1&&e[i].b==1000000000)bj=i;
if(bj==-1)bj=n+1;//第一个不是协作州的地方
_f(i,1,n)
{
_f(j,1,n)tmp[j]=e[j];
sort(tmp+i,tmp+n+1,cmp);
_f(j,i,n)
g[i][j-i+1]=tmp[j].a+g[i][j-i];
}
// _f(i,1,n)
// _f(j,1,n)
// chu("g[%d][%d]:%d\n",i,j,g[i][j]);
int bianjie=min(K,bj-1);//找多少协作州,最多的
double minans=1e9;
//chu("bianjie:%d\n",bianjie);
_f(b,0,min(K-1,bj-1))//找多少协作州
{
_f(i,0,bianjie)
_f(j,0,b)
f[i][j]=1e9;
f[0][0]=0;
_f(i,1,bianjie)//考虑前多少个州(选的)
{
f[i][0]=f[i-1][0]+(double)e[i].a/(b+1);//不当做协作州
_f(j,1,min(b,i))//有几个协作州
{
f[i][j]=min(f[i-1][j]+(double)e[i].a/(b+1),f[i-1][j-1]+(double)e[i].b/(j));
}
}
_f(i,b,bianjie)//考虑前多少个里面找协作州
{
// chu("(%d %d)%lf+%lf\n",i,b,f[i][b],(double)g[i+1][K-i]/(b+1));
minans=min(minans,f[i][b]+(double)g[i+1][K-i]/(b+1));
}
// chu("rod:%d mians:%lf\n",b,minans);
}
chu("%.15lf",minans);
return 0;
}
/*
3
3
1 5
2 3
4 5
*/
T2:给你2个栈,n个元素的入栈出栈次序,求元素属于栈的方案数。(n<=1e6)
发现如果([)]一定不能放一起,有矛盾关系,并查集维护,最后答案就是$2 ^ \(联通快个数。找块\)O(n^2)$。考虑优化,只枚举对于一个右边界xl,在[xl,xr]区间第一次出现的元素,连边O(E),nxt[x]表示入栈次序是x的元素下一个最远存在矛盾关系的位置,搞一个并查集维护对于pos位置,pos以及之后到当前遍历的满足右端点没有出现的位置(get父亲)。这样你在遍历[xl,xr]区间,每次跳getfa(nxt[x]+1),然后维护j=cnt_now(交叉最远),j一定就是具有矛盾关系的。注意先把fa[x]=getfa(x+1)一下,就可以了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1e6+10;const int mod=1e9+7;
vector<int>e[N];
int col[N];
int nxt[N],fa[N],le[N],id[N<<1],a[N],cnt;
int n;
ll ans=1;
void qm(int a,int b)
{
while(b)
{
if(b&1)ans=(ll)ans*a%mod;
b>>=1;
a=(ll)a*a%mod;
}
}
inline int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
inline void addedge(int u,int v)
{
e[u].push_back(v);e[v].push_back(u);
}
inline int dfs(int u)
{
for(int i=0;i<e[u].size();++i)
{
int v=e[u][i];
if(col[v]!=0&&col[v]==col[u])return 0;
if(col[v]==0)
{
col[v]=-col[u];
if(!dfs(v))return 0;
}
}
return 1;
}
int main()
{
// freopen("1.in","r",stdin);
//freopen("a.out","w",stdout);
int l,r;
n=re();
_f(i,1,n)
{
int l=re(),r=re();
id[l]=id[r]=i;
}
_f(i,1,n)nxt[i]=i,fa[i]=i;
fa[n+1]=n+1;
_f(i,1,n<<1)
{
int p=id[i];
if(!le[p])a[++cnt]=p,le[p]=cnt;
else
{
int u=le[p];
fa[u]=find(u+1);
for(int j=fa[u],k;j<=cnt;j=k)
{
addedge(a[j],p);k=find(nxt[j]+1);nxt[j]=cnt;
}
}
}
int cnt=0;
memset(col,0,sizeof(col));
_f(i,1,n)
{
if(col[i]==0)
{
col[i]=1;if(dfs(i))++cnt;
else
{
chu("0");return 0;
}
}
}
qm(2,cnt);
chu("%lld",ans);
return 0;
}
/*
7
4
4 -1
11 -1
6 -1
12 -1
36 -1
11 -1
20 -1
4 4
3 3
4 4
6 6
7 7
*/
T3:一个一维房间,从i-->i+1需要钥匙c[i],每个房间有Bi把钥匙,互不相同,你可以捡钥匙q组询问,(x,y),问从x到y行不行。(q,n<=5e5)
L[]R[]维护向左向右最多到的位置,r[x]维护如果要向左,向右第一个出现c(i-1)的位置.先从左边扫一下R[x],然后倒着扫一遍L,同时更新R,这样每次跳的区段就很大,会比较快。
至于O(n)的...
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=500010;
vector<int> key[N];
int n,q,c[N],l[N],r[N],L[N],R[N],num[N],la[N];
int main(){
scanf("%d",&n);
for (int i=1; i<n; ++i) scanf("%d",&c[i]);
//cerr<<"END"<<endl;
for (int i=1; i<=n; ++i){
scanf("%d",&num[i]);
for (int j=1; j<=num[i]; ++j){
int x; scanf("%d",&x);
key[i].emplace_back(x);
}
}
//cerr<<"END2";
for (int i=1; i<=n; ++i){
for (auto j:key[i])
la[j]=i;
l[i]=la[c[i]];
}
for (int i=1; i<=n; ++i) la[i]=n+1;
for (int i=n; i>=1; --i){
for (auto j:key[i])
la[j]=i;
r[i]=la[c[i-1]];
}
R[n]=n;
for (int i=n-1; i>=1; --i){
if (l[i]<i){
R[i]=i;
continue;
}
R[i]=R[i+1];
while (R[i]<n&&l[R[i]]>=i) R[i]=R[R[i]+1];
}
//for (int i=1; i<=n; ++i) cerr<<i<<" "<<R[i]<<endl;
//return 0;
L[1]=1;
for (int i=2; i<=n; ++i){
if (r[i]>R[i]){
L[i]=i;
continue;
}
if (R[i-1]>=i){
L[i]=L[i-1];
R[i]=R[i-1];
continue;
}
L[i]=L[i-1];
while (1){
bool fl=0;
if (L[i]>1&&r[L[i]]<=R[i]){
fl=1;
L[i]=L[L[i]-1];
}
if (R[i]<n&&l[R[i]]>=L[i]){
fl=1;
R[i]=R[R[i]+1];
}
if (!fl) break;
}
//cerr<<i<<" "<<L[i]<<" "<<R[i]<<endl;
//return 0;
}
//for (int i=1; i<=n; ++i) cerr<<L[i]<<" "<<R[i]<<endl;
//cerr<<"UP";
scanf("%d",&q);
//cerr<<"FUCK";
for (int i=1; i<=q; ++i){
int x,y; scanf("%d%d",&x,&y);
cout<<(L[x]<=y&&y<=R[x]?"YES\n":"NO\n");
}
}
T4:贪心+DP+斜率优化
https://www.cnblogs.com/Itst/p/10623598.html
注意因为Min不单调,所以不能删除队尾,需要二分斜率,[L,R),mid-1一定存在,边界考虑一下。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=2e5+100;
#define int ll
int X,n,m,W,T;
int s[N],st[N],pre[N],f[N],top;//休息点
struct Node
{
int D,C,Min;
inline bool operator<(const Node&U)const
{
return D<U.D;//按照取水次序
}
}ps[N];
pair<int,int>t[N];
#define S(x) (f[x]-pre[x])
inline void insert(int x)
{
while(top>1&&(S(st[top])-S(st[top-1]))*(x-st[top])>=(S(x)-S(st[top]))*(st[top]-st[top-1]))--top;
st[++top]=x;
//(S(st[top])-S(st[top-1]))*(x-st[top])>=(S(x)-S(st[top]))*(st[top]-st[top-1])
}
inline int getans(int x)
{
int l=1,r=top+1;
while(r-l>1)//[l,r),mid-1一定存在
{
int mid=(l+r)>>1;
if(S(st[mid])-S(st[mid-1])<=x*(st[mid]-st[mid-1]))l=mid;
else r=mid;
}
return st[l];
}
signed main()
{
// freopen("1.in","r",stdin);
//freopen("a.out","w",stdout);
X=re(),n=re(),m=re(),W=re(),T=re();
_f(i,1,n)
{
s[i]=re();t[i]=make_pair(s[i]%T,s[i]);
}
_f(i,1,m)
{
ps[i].D=re()%T;ps[i].C=re();
}
t[n+1]=make_pair(X%T,X);
sort(t+1,t+2+n);int p=1;
sort(ps+1,ps+1+m);
ps[m+1].D=X+1;
_f(i,1,m)//给每个乘客找
{
//Di<s%T<D(i+1)
ps[i].Min=1e17;
while(p<=n+1&&t[p].first<ps[i].D)++p;
while(p<=n+1&&t[p].first<ps[i+1].D)
{
ps[i].Min=min(ps[i].Min,t[p].second/T);
++p;
}
pre[i]=pre[i-1]+ps[i].C;
}
++top;//有一个空
//Min处理好了
memset(f,0x3f,sizeof(f));
f[0]=0;//考虑前i个乘客的最小花费
_f(i,1,m)
{
f[i]=f[i-1]+((X-ps[i].D)/T+1)*W;
if(ps[i].Min!=(ll)1e17)
{
int j=getans(W*ps[i].Min);
f[i]=min(f[i],f[j]+pre[i]-pre[j]+W*(i-j)*ps[i].Min);
}
insert(i);
}
chu("%lld",f[m]+(X/T+1)*W);
return 0;
}
/*
*/