1. 程式人生 > 其它 >訓練套題裡的一些題

訓練套題裡的一些題

D. Disposable Switches

顯然最終的答案只與經過的邊數和經過的最短路徑長度和有關

\(dis[x][k]\)表示到\(x\)點經過\(k\)條邊的最短路(因為記錄了邊數,這個東西可以直接\(n^2\)預處理

完全不可能的點不容易求,考慮求哪些點有可能在\(1\rightarrow n\)的最短路上

考慮所有\(dis[n][x]\),對應的答案即為\(res=c\cdot x+\frac{dis[n][x]}{v}\)

相當於求對所有\(<c,v>\)的可能取值,有哪些\(x\)對應的\(res\)有可能對應最小值

不妨固定\(v\),此時令\(y=\frac{dis[n][x]}{v}\)

,變形得:\(y=-c\cdot x+res\),是一個直線的式子

只需要考慮對所有\(c\ge 0\),哪些點\((x,y)\)可能對應\(res_{min}\)

顯然只需要求一個下凸殼,同時由於\(c\ge 0\)只需要這個下凸殼中斜率小於0的部分

求出所有可能的\((x,y)\)後,所有能轉移出\((x,dis[n][x])\)的點都是有用的

倒著\(dfs\)一遍並打標記即可

#include<bits/stdc++.h>
#define ll long long
#define db double
#define MAXN 100100
#define pii pair<int,int>
#define fi first
#define se second
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pb push_back
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0',ch=getchar();}
	return x*f;
}
ll inf=1e18;
int n,m,nxt[20010],fst[2020],to[20010],cnt,val[20010];
ll dis[2020][2020];
int st[2020],tp,ok[2020];
pair<int,ll> g[2020];
void add(int u,int v,int w){nxt[++cnt]=fst[u],fst[u]=cnt,to[cnt]=v,val[cnt]=w;}
void getdis()
{
	rep(i,1,n) rep(j,0,n) dis[i][j]=inf;dis[1][0]=0;
	rep(k,0,n) rep(x,1,n-1) if(dis[x][k]!=inf)
		ren if(dis[to[i]][k+1]>dis[x][k]+val[i])
			dis[to[i]][k+1]=dis[x][k]+val[i];
}
inline ll Y(int a,int b){return g[b].se-g[a].se;}
inline ll X(int a,int b){return g[b].fi-g[a].fi;}
int tag[2020],ans;
void dfs(int x,int num)
{
	if(tag[x]) return ;tag[x]=1;
	ok[x]=1;if(!num) return ;
	ren if(dis[to[i]][num-1]+val[i]==dis[x][num]) dfs(to[i],num-1);
}
int main()
{
	n=read(),m=read();int x,a,b,c;
	rep(i,1,m) a=read(),b=read(),c=read(),add(a,b,c),add(b,a,c);
	getdis();m=0;
	rep(i,1,n) if(dis[n][i]!=inf) g[++m]={i,dis[n][i]};
	tp=0;rep(i,1,m)
	{
		while(tp>=2&&Y(st[tp],i)*X(st[tp-1],st[tp])<Y(st[tp-1],st[tp])*X(st[tp],i))
			tp--;
		st[++tp]=i;
	}
	while(tp>=2&&Y(st[tp-1],st[tp])>0) tp--;
	rep(i,1,tp) 
	{
		rep(j,1,n) tag[j]=0;
		dfs(n,g[st[i]].first);
	}
	rep(i,1,n) if(!ok[i]) ans++;
	printf("%d\n",ans);
	rep(i,1,n) if(!ok[i]) printf("%d ",i);
}

H. Height Profile

最終答案區間顯然至少有一個端點是整點,否則可以通過向斜率更大的區間平移得到更大答案

則列舉左端點的整點\(l\),找到右側滿足條件的最遠整點\(r\),即\(h_r-h_l\ge(r-l)g\)

移項得:\(h_r-r\cdot g\ge h_l-l\cdot g\),令\(w_i=h_i-i\cdot g\),線上段樹上二分容易得到\(r\)

對於這個\([l,r]\)分別計算向左延伸和向右延伸的答案即可

#include<bits/stdc++.h>
#define ll long long
#define db double
#define inf 2139062143
#define MAXN 100100
#define pii pair<int,int>
#define fi first
#define se second
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define pb push_back
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0',ch=getchar();}
	return x*f;
}
int n,m,res,h[MAXN],g,mx[MAXN<<2],w[MAXN];
db ans;
void build(int k,int l,int r)
{
	if(l==r) {w[l]=mx[k]=h[l]-g*l;return ;}int mid=l+r>>1;
	build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	mx[k]=max(mx[k<<1],mx[k<<1|1]);
}
void find(int k,int l,int r,int a,int b,db w)
{
	if(a<=l&&r<=b)
	{
		if(mx[k]<w) return ;
		if(l==r) {res=max(l,res);return ;}
		int mid=l+r>>1;
		if(mx[k<<1|1]>=w) find(k<<1|1,mid+1,r,a,b,w);
		else find(k<<1,l,mid,a,b,w);
		return ;
	}
	int mid=l+r>>1;
	if(a<=mid) find(k<<1,l,mid,a,b,w);
	if(b>mid) find(k<<1|1,mid+1,r,a,b,w);
}
int main()
{
	n=read(),m=read();rep(i,0,n) h[i]=read();
	int l,r;db tmp;
	while(m--)
	{
		scanf("%lf",&tmp);g=floor(tmp*10+0.1);
		build(1,0,n);ans=-1;
		rep(i,0,n-1)
		{
			res=0;find(1,0,n,i+1,n,w[i]);
			if(!res) continue;
			l=i,r=res;ans=max(ans,(db)res-i);
			if(l&&w[l-1]>w[r])
			{
				tmp=(1.0*w[r]-w[l])/(1.0*w[l-1]-w[l]);
				ans=max(ans,res-i+tmp);
			}
			if(r<n)
			{
				tmp=(1.0*w[r]-w[l])/(1.0*w[r]-w[r+1]);
				ans=max(ans,res-i+tmp);
			}
		}
		printf("%.8lf\n",ans);
	}
}

J. Jackdaws And Crows

列舉\(fake\ accounts\)的個數\(x\),則所有絕對值小於\(x\)的位置的取值可正可負,視為是自由點

顯然只有當\(x\)為原序列中某數絕對值\(+1\)時,會影響自由點

將原序列按照絕對值排序,列舉時可以用連結串列快速維護剩餘的非自由點

問題轉化為如何快速求在當前這個狀態需要刪除多少個數使滿足條件

  • 顯然刪除非自由點比刪除自由點更優
  • 最後需要刪除的個數即為在連結串列中相鄰的非自由點中的矛盾對數,可以簡單證明:令非自由點序列為\(a_i\),當\(<a_i,a_{i+1}>\)矛盾時,顯然\(<a_i,a_{k}>\)\(<a_{i+1},a_k>\)一定只有一個矛盾,其中\(k> i+1\)。當選擇刪除\(a_{i+1}\)時,\(<a_i,a_k>\)的矛盾變化即變化後與原先的\(<a_{i+1},a_k>\)相同。即:每次刪除一個非自由點之後只能使相鄰的矛盾對數減一,因此最終答案為連結串列中相鄰的非自由點中的矛盾對數。

這個答案在初始\(O(n)\)統計一次答案之後,每次刪除一個數的時候可以\(O(1)\)快速維護

(初始存在0的時候,需要考慮將0全部刪掉的情況

#include<bits/stdc++.h>
#define ll long long
#define db double
#define MAXN 500100
#define pii pair<int,int>
#define fi first
#define se second
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pb push_back
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0',ch=getchar();}
	return x*f;
}
int n,res,las,l[MAXN],r[MAXN];
ll p,q,a[MAXN],ans;
pii g[MAXN];
inline int calc(int x,int y)
{
	if(!(x*y)) return 0;
	return (a[x]*a[y]>0)^((y-x+1)&1);
}
int main()
{
	n=read(),p=read(),q=read();rep(i,1,n) a[i]=read(),g[i]={abs(a[i]),i};
	rep(i,1,n)
	{
		if(a[i]==0) {res++;continue;}
		if(las&&a[las]*a[i]>0) res++;
		las=i;
	}
	ans=res*q;sort(g+1,g+n+1);
	las=res=0;int x;
	rep(i,1,n) if(a[i])
		{if(las) r[las]=i,res+=calc(las,i);l[i]=las,las=i;}
	ans=min(ans,q*res+p);
	rep(i,1,n) if(g[i].fi)
	{
		x=g[i].se;res-=calc(l[x],x)+calc(x,r[x])-calc(l[x],r[x]);
		if(r[x]) l[r[x]]=l[x];if(l[x]) r[l[x]]=r[x];
		ans=min(ans,p*(g[i].fi+1)+q*res);
	}
	printf("%lld\n",ans);
}