CSP-S模拟8 选举 港口设施 幽深府邸 长途巴士

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[1~ K][b]后,我们枚举第一维位置,代表前1~ i个交杂支持和协助,剩下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:一段旅程,起点、终点、加水站可以加水,加水站n个,每个在Si时间可以加水,司机必须在KT时间加水,有m个乘客,分别在KT+Di时刻需要加水,如果乘客没水,就会下车,付出Ci代价,司机必须有水,问最小代价。(买水每单位W元,保证起点终点加水站没有乘客司机要喝水)n,m<=2e5

首先简化问题,发现如果要乘客下车,一定是从Si到Sj的加水站的一段后缀,否则一定要么不合法要么不优,而且如果要乘客下车一定越早越好。所以可以设\(dp[i]:表示处理了前i个乘客的最小花费\)
转移:
如果i喝水,把所有贡献算上:\(dp[i]=dp[i-1]+W*((X-e[i].D)/T+1)\)
如果不喝水,就从0 ~ i-1选一个j,从j+1~i都不喝水,下车。
提前算出Mini表示i下车的最早时间(多少段完整的T区间,最后一次下车了不用算),
\(Mini=min(Si/T)(Di<Si%T <Di+1)\)
\(dp[i]=min(dp[i],dp[j]+sumi-sumj+Min*(i-j)*M)\)sum是预处理的C的前缀。\(O(n^2)\)
然后就可以斜率优化了。
\(dpj-sumj=(W*Mini)*j+dpi-sumi-W*i*Mi,k=(W*Mini),截距最小\),维护下凹包,但是虽然j是单调递增的,因为K不单调,所以需要在决策的时候找到斜率在x之间的点,二分就行。[L,R),mid-1一定存在,所以mid就是决策点。必须保证mink<x.最后保留l输出。当最左边是最优决策点的时候,R=1+1直接开了,L=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;
}
/*
*/
posted on 2022-09-21 21:52  HZOI-曹蓉  阅读(48)  评论(0)    收藏  举报