9.21-CSP-S模拟8

T1 JOI 2022 Final T3「選挙で勝とう / Let's Win the Election」

赛时对着一个假贪心不知道为什么是错的。

贪心就是枚举选几个协作州,然后贪心把小的都拿了,剩下的支持州挑小的。
可能会出现某个协作州的 \(b\) 小,但是 \(a\) 更小,导致将他与某个支持州交换反而更优。

于是需要dp,考虑我们要选出 \(p\) 个支持州,设 \(f_{i,j}\) 表示在前 \(i\) 个州里,选出 \(j\) 个协作州的最小时间,我们发现前 \(i\) 个州是不可能不选的,要么是协作州,要么是支持州。因为如果有一个州没选,那么我把其后选的一个协作州换成它显然更优。于是还是枚举协作州的数量,然后做一次dp,统计答案时枚举最后一个协作州是谁,然后从它之后的州里贪心的选 \(a\) 小的即可。

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=5e2+10,INF=1e9;
const double eps=1e-4;


int n,K,mx;

struct Ver{double a,b;}E[maxn];

bool cmp1(const Ver &x,const Ver &y){return x.b<y.b;}
bool cmp2(const Ver &x,const Ver &y){return x.a<y.a;}

double pre[maxn];
double f[maxn][maxn];
double ans=INF;

multiset<double>S,rec;

void Get(int Te){
	Rep(i,0,n)Rep(j,0,n)f[i][j]=INF;
	f[0][0]=0;
	Rep(i,1,K){
		Rep(j,0,min(Te,i)){
			f[i][j]=min(f[i][j],f[i-1][j]+E[i].a/(Te+1));
			if(j>=1 && abs(E[i].b-INF)>eps)f[i][j]=min(f[i][j],f[i-1][j-1]+E[i].b/j);
		}
	}
	S=rec;
	Dwn(i,K,0){
		double te=0;
		//Rep(j,i+1,K)te+=E[j].a;
		auto it = S.begin();
		Rep(j,i+1,K)te+=(*it),++it;
		ans=min(ans,f[i][Te]+te/(Te+1));
		S.insert(E[i].a);
	}
}

void solve(){
	cin>>n>>K;
	Rep(i,1,n){ cin>>E[i].a>>E[i].b; if(E[i].b==-1)E[i].b=INF;else ++mx; } 
	sort(E+1,E+n+1,cmp1);
	Rep(i,K+1,n)rec.insert(E[i].a);
	Rep(i,0,min(K-1,mx))Get(i);
	cout<<fixed<<setprecision(10)<<ans<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }


T2 JOISC 2017 Day1 T2「港湾設備 / Port Facility」

其实并查集做是很容易想的,即时删点保证连边复杂度也是很容易想的,零分也是很容易拿的

因为即时删点的并查集做法是没有办法把边连全的,所以根本判不了无解,如果题目保证有解的话就比较美妙了。

正解就是不删点,而是维护联通块,然后维护nxt指针指向下一个联通块,连边后就把他们合并起来,然后在图上贪心做染色就能判无解

T3 JOISC 2017 Day3 T2「細長い屋敷 / Long Mansion」

考虑暴力扩展的做法,把当前拥有的钥匙用某种东西维护起来显然是劣的,考虑优化我们判断能不能开一个门的方法。

假设我从 \(x\) 点开始扩展,当前已经扩展到的区间为 \([l,r]\),如果我们想要扩展到 \(r+1\),那么 \([l,r]\) 内至少有一个屋子里有 \(c_{r+1}\),因为我从左向右走,只要离 \(r+1\) 最近的出现了 \(c_{r+1}\) 的位置已经被扩展到即可,于是我们只需要维护一个 \(L_i\) 表示 \(c_i\) 在之前的最近一次出现的位置。向 \(l-1\) 扩展同理。

暴力扩展每一个点是 \(n^2\) 的,考虑优化它。假设我们现在已经扩展完了一个点,正在扩展的另一个点此时扩展到了它,那么这个点所有能到的位置正在扩展的点一定都能到,于是我们可以直接对两者的答案取并集给当前扩展的点。这样进行类似记忆化搜索,每次我要扩展一侧边界时,就从那个边界开始做一次扩展,然后取并集即可,如果已经扩展过了就直接返回跑路。

复杂度 \(O(n)\),实测比随机化快一点

点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=5e5+10;

int n,q;
int c[maxn];
vector<int>vec[maxn];
int last[maxn];
int L[maxn],R[maxn],p[maxn];
bool vis[maxn];
pii rg[maxn];

void Sol(int x){
	bool flag=false;
	if(vis[x])return void();
	if(rg[x].fir>0)return;
	vis[x]=true;
	int &l =rg[x].fir,&r=rg[x].sec;
	l=x,r=x;flag=true;
	while(flag){
		flag=false;
		if(l>1 && R[l-1]>=l && R[l-1]<=r)
		{ Sol(l-1);flag=true; l=min(l,rg[l-1].fir),r=max(r,rg[l-1].sec); }
		if(r<n && L[r]<=r && L[r]>=l)
		{ Sol(r+1);flag=true; l=min(l,rg[r+1].fir),r=max(r,rg[r+1].sec); }
	}
}

void solve(){
	cin>>n;int x,y;
	for(int i=1;i<n;++i)cin>>c[i];
	Rep(i,1,n){
		cin>>y; Rep(j,1,y){ cin>>x;vec[i].push_back(x);last[x]=i; }
		if(i!=n)L[i]=last[c[i]];
	}
	Rep(i,1,n)last[i]=n+1;
	Dwn(i,n,1){ for(auto it : vec[i])last[it]=i; R[i-1]=last[c[i-1]]; }
	Rep(i,1,n)Sol(i);
	cin>>q;
	while(q--){
		cin>>x>>y;
		if(rg[x].fir<=y && rg[x].sec>=y)cout<<"YES\n";
		else cout<<"NO\n";
	}
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }


T4 JOISC 2017 Day3 T1「長距離バス / Long Distance Coach」

发现几个性质

  • 每次下车的都是一段后缀
  • 如果某个人会下车,那么一定尽早下
  • 水的价格是相同的,于是可以每次到服务站时水箱都是空的
  • 如果一个人作为被删的后缀的末尾,那么它某次喝水的时间一定与下一个服务站到达的时间相邻(中间没有其他人要喝水)

于是我们可以按照一个 \(T\) 内喝水的时间对乘客排序,逐个考虑其是否下车。设 \(f_i\) 表示前 \(i\) 个人的最小费用

如果不下车

\[f_i=f_{i-1}+W \times (\lfloor \frac{X}{T} \rfloor+ D_i \leq X \bmod T) \]

如果下车

\[f_i=\min_{1\leq j < i} f_j+pre_i-pre_j+W \times mt_i \times (i-j) \]

\(pre_i\) 为前 \(i\) 个人退费的和,\(mt_i\) 为第 \(i\) 个人最少在喝几次水后下车。\(mt\) 可以把所有服务站出现的时间对周期取模后排序,单指针扫一遍处理。

由于司机一定不能下车,每个点只能从比靠前的转移,转移不会成环。下车的转移是套路的斜率优化。

点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define int ll
using namespace std;

const int maxn=2e5+10,INF=1e18;

int X,n,m,W,T;

int s[maxn];
struct Per{
	int D,C,mini;
	bool operator<(const Per &rhs)const{ return D<rhs.D; }
}E[maxn];

int f[maxn];
int pre[maxn];
pii t[maxn];

int q[maxn],top;

#define S(x) (f[x]-pre[x]) 

int Query(int x){
	int l=1,r=top+1;
	while(r-l>1){
		int mid=(l+r)>>1;
		if(x*(q[mid]-q[mid-1])>(S(q[mid])-S(q[mid-1])))l=mid;
		else r=mid;
	}
	return q[l];
}

void solve(){
	cin>>X>>n>>m>>W>>T;
	Rep(i,1,n)cin>>s[i],t[i]=mair(s[i]%T,s[i]);
	t[++n]=mair(X%T,X);
	Rep(i,1,m)cin>>E[i].D>>E[i].C,E[i].mini=INF;
	sort(E+1,E+m+1);sort(t+1,t+n+1);
	E[m+1].D=T;
	int p=1;
	Rep(i,1,m){
		while(p<=n && t[p].fir<E[i].D)++p;
		while(p<=n && t[p].fir<E[i+1].D)E[i].mini=min(E[i].mini,t[p++].sec/T);
		pre[i]=pre[i-1]+E[i].C;
	}
	++top;
	Rep(i,1,m){
		f[i]=f[i-1]+W*((X/T)+(X%T>=E[i].D ? 1 : 0));
		if(E[i].mini<INF){ int j=Query(W*E[i].mini); f[i]=min(f[i],f[j]+pre[i]-pre[j]+W*E[i].mini*(i-j)); }
		while(top>1 && (S(x)-S(q[top]))*(q[top]-q[top-1])<=(S(q[top])-S(q[top-1]))*(x-q[top]))--top;
		q[++top]=x;
	}
	cout<<f[m]+(X/T+1)*W<<"\n";
}

#undef int 
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

posted @ 2022-09-21 21:18  Delov  阅读(41)  评论(0编辑  收藏  举报