2018-2019 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2018)

终于补完了(颓了n天因为舍友都在颓我也不想学



9.17 2018-2019 ACM-ICPC Nordic Collegiate Programming Contest (NCPC 2018)

比赛链接CF GYM
校内OJ

BCJ貌似签到题(不是我做的 不补了)
FG直接咕了(以前学长都没补)
H自闭,写错变量WA了n次。(我其实还没太懂)不管。
I注意条件就很简单,就是要高精。


A.Altruistic Amphibians(01背包)

(Description)
(n)只青蛙要过宽度为(d)的河,每只青蛙有重量(w)、高度(h)、跳跃距离(l)
若干只青蛙可以叠罗汉,但要保证每只青蛙(i)上面的所有青蛙重量和小于(w_i)。此时若青蛙(i)下面青蛙高度和(sum h+l_i>d)(i)可以跳过河。
只要满足条件每只青蛙可以任意次堆叠。求最多有多少只青蛙能过河。

(Solution)
英语题面读漏条件就很难受...要注意重量和是严格小于(w_i),且青蛙总重要(leq 1e8)
因为青蛙可以任意次叠罗汉,所以单独的一只青蛙(i)可以为总重(< w_i)的青蛙提供(h_i)的贡献,且只会重的去贡献轻的,
每只青蛙可以选择贡献或不贡献,所以就是一个01背包了。。。
青蛙从重到轻排序,(f[i])表示可承重为(i)时的最大高度,青蛙(k)的影响为:(f[i]=max(f[i], f[i+w_k]+h_k))
注意(k)能更新的(i)范围只有(1sim w_i-1),所以总复杂度只有(O(n+sum w_i))
至于统计答案,只要(f[w_k]+l_k>d)青蛙(k)就一定有办法过了。。

//218ms	392400KB
#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
typedef long long LL;
const int N=1e5+5,M=1e8+3;

int f[M];
struct Frog
{
	int l,w,h;
	bool operator <(const Frog &x)const
	{
		return w>x.w;
	}
}A[N];

inline int read()
{
	int now=0;register char c=gc();
	for(;!isdigit(c);c=gc());
	for(;isdigit(c);now=now*10+c-48,c=gc());
	return now;
}

int main()
{
	int n=read(),D=read(),s=0;
	for(int i=1; i<=n; ++i) A[i]=(Frog){read(),read(),read()}, s+=A[i].w;
	std::sort(A+1,A+1+n);
	int ans=0;
	for(int i=1; i<=n; ++i)
	{
		int w=A[i].w, h=A[i].h;
		if(A[i].l+f[w]>D) ++ans;
		for(int j=1; j<w && j+w<=s; ++j) f[j]=std::max(f[j],f[j+w]+h);
	}
	printf("%d
",ans);

	return 0;
}

D.Delivery Delays(二分 DP)

(Description)
给定一张图,走1距离花费1时间。H在1号点有一家披萨店。有(k)个订单,每个订单有收到订单时间(s_i),需送到的位置(u_i),披萨被做好的时间(t_i)
披萨(i)(t_i)时间在1号店做好后,H才能在1号店拿起任意多披萨去送。每一时刻H可以带任意多的披萨,但必须严格按照订单顺序送披萨。
最小化 每个订单得到披萨所需的等待时间的最大值。

(Solution)
因为严格按照顺序送披萨,所以可以(f[i])表示送完前(i)个披萨并还留在(u_i)位置所需的最短时间。
但怎么使最大值最小化呢。。二分答案(x),设送到时间为(p_i),满足(x-s_igeq p_i),即(x-p_igeq s_i)则可以转移。
(d_{i,j})(i)(j)距离。然后考虑一次拿起(i+1sim j)的披萨去送,花的时间是(d_{1,i}+max_{k=i+1sim j}t_k+d_{i+1,i+1}+...+d_{j-1,j}),对每个(l∈[i+1,j])要有(x-(f[i]+d_{1,i}+max_{k=i+1sim l}t_k+d_{i+1,i+1}+...+d_{l-1,l})-s_igeq 0)
发现只要拿(f[i])去依次更新(f[j](j>i))时可以维护几个量,保证满足条件。所以转移就好了。。(具体不想写了。。)

//576ms	9300KB 比赛时写的有点乱
#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
#define pc putchar
#define pr std::pair<LL,LL>
using namespace std;
typedef long long LL;
const int N=1005,M=1e5+6;
const LL INF=1e16;

int Enum,H[N],nxt[M],to[M],val[M],u[N],s[N],t[N];
LL d[N][N],dis[N],f[N];
bool vis[N];
std::priority_queue<pr> q;

inline int read()
{
	int now=0; char c=gc();
	for(; !isdigit(c); c=gc());
	for(; isdigit(c); now=now*10+c-48,c=gc());
	return now;
}
inline void AE(int u,int v,int w)
{
	to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, val[Enum]=w;
	to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum, val[Enum]=w;
}
void Dijkstra(int s)
{
	memset(vis,0,sizeof vis);
	memset(dis,0x3f,sizeof dis);
	dis[s]=0, q.push(pr{0,s});
	while(!q.empty())
	{
		int x=q.top().second; q.pop();
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=H[x],v; i; i=nxt[i])
			if(dis[v=to[i]]>dis[x]+val[i])
				dis[v]=dis[x]+val[i], q.push(pr{-dis[v],v});
	}
}
bool Check(LL x,int n)
{
	if(x+s[1]-(t[1]+d[1][u[1]])<0) return 0;
	f[0]=0;
	for(int i=1; i<=n; ++i) f[i]=INF;
	for(int i=0; i<n; ++i)
	{
		if(f[i]==INF) continue;
		int maxt=0,pi=u[i];
		LL sum=0,now=INF,nowmax=0,tmp=f[i]+d[1][pi];
		for(int j=i+1; j<=n; ++j)
		{
			int pj=u[j];
			if(j!=i+1) sum+=d[u[j-1]][pj];
			else sum+=d[1][pj];
			maxt=std::max(maxt,t[j]);
			now=std::min(now,x+s[j]-tmp-sum);
			nowmax=std::max(nowmax,std::max(maxt-tmp,0ll));
			if(now<nowmax) break;
			if((x+s[j])-(tmp+sum+std::max(maxt-tmp,0ll))>=0)
				f[j]=std::min(f[j],f[i]+d[1][pi]+sum+std::max(maxt-(f[i]+d[1][pi]),0ll));
		}
		
	}
	return f[n]<INF;
}

int main()
{
	int n=read(),m=read();
	for(int i=1,u,v; i<=m; ++i) u=read(),v=read(),AE(u,v,read());
	for(int i=1; i<=n; ++i)
	{
		Dijkstra(i);
		for(int j=1; j<=n; ++j) d[i][j]=dis[j];
	}
	for(int i=1; i<=n; ++i) d[0][i]=d[i][0]=0;
	int k=read();
	for(int i=1; i<=k; ++i) s[i]=read(), u[i]=read(), t[i]=read();
	LL l=0,r=INF,mid;
	while(l<r)
	{
		if(Check(mid=l+r>>1,k)) r=mid;
		else l=mid+1; 
	}
	printf("%lld
",l);

	return 0;
}

E.Explosion Exploit(记忆化搜索)

(Description)
你有(n)个小兵,对方有(m)个小兵,血量给定。随机对场上所有还存活的小兵造成(1)伤害,重复(d)次。求(d)次伤害后对方所有小兵死亡的概率(你的小兵是否存活随意)。

(Solution)
这题不会..我怕不是个傻子...(以为排序后状态数依旧很多)
直接DP,最简单的就是开个10维数组了,复杂度(O(7^{10}))
容易发现具体哪个小兵的血量是多少是无所谓的,只需要记血量为(i)的小兵有多少个,也就是将某时刻的血量排序作为一种状态就可以了。
写个程序算下排序后的状态数,只有462*462=213444个。直接记忆化就ok了。
血量可以七进制来存,也可以用10位longlong+map,或者存每个血量的有多少个(转移方便)。注意剪下枝。
第三种存状态的话s<1e6时敌方就都挂了。(0血量不用存啊。。)
(打ACM就随便用STL了

//124ms	8000KB
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <unordered_map>
#define gc() getchar()
typedef long long LL;
const int N=250005;

namespace TEST
{
	const int N=1e5+5;
	int ans;
	bool v[N];
	int Trans(int x)
	{
		static int a[6];
		for(int i=1; i<=5; ++i,x/=10) a[i]=x%10;
		std::sort(a+1,a+6);
		if(a[5]>6) return 0;
		x=0;
		for(int i=1; i<=5; ++i) x*=10, x+=a[i];
		return x;
	}
	void Main()
	{
		for(int i=0,t; i<100000; ++i)
			if(!v[t=Trans(i)]) v[t]=1, ++ans;
		printf("%d
",ans);
	}
}

int A[8],B[8];
std::unordered_map<LL,double> f;

inline int read()
{
	int now=0;register char c=gc();
	for(;!isdigit(c);c=gc());
	for(;isdigit(c);now=now*10+c-48,c=gc());
	return now;
}
LL Trans()
{
	LL x=0;
	for(int i=1; i<=6; ++i) x=x*10+B[i];
	for(int i=1; i<=6; ++i) x=x*10+A[i];
	return x;
}
double DFS(LL s,int d)
{
	if(s<1000000) return 1;
	if(f.count(s)) return f[s];
	if(!d) return 0;
	double res=0;
	for(int i=1; i<=6; ++i)
		if(A[i]) --A[i], ++A[i-1], res+=(A[i]+1)*DFS(Trans(),d-1), ++A[i], --A[i-1];
	for(int i=1; i<=6; ++i)
		if(B[i]) --B[i], ++B[i-1], res+=(B[i]+1)*DFS(Trans(),d-1), ++B[i], --B[i-1];
	int sum=0;
	for(int i=1; i<=6; ++i) sum+=A[i]+B[i];
	return f[s]=res/sum;
}

int main()
{
	const int n=read(),m=read(),d=read();
	for(int i=1; i<=n; ++i) ++A[read()];
	for(int i=1; i<=m; ++i) ++B[read()];
	printf("%.9lf
",DFS(Trans(),d));

	return 0;
}

K.King's Colors(容斥/二项式反演)

(Description)
给定一棵(n)个节点的树和(k),求用恰好(k)种颜色将每个点染色,且相邻点不同色的方案数。

(Solution)
(f(k))表示最多用(k)种颜色染色的方案数。树的形态是无所谓的,只需要每个点颜色不同与父节点,即对于(f(k))根节点有(k)种选择,非根节点都是(k-1)种选择,即(f(k)=k*(k-1)^{n-1})
(g(k))表示恰好用(k)种颜色染色的方案数。

容斥有:(g(k)=f(k)-C_k^{k-1}f(k-1)+C_k^{k-2}f(k-2)-...)(g(k)=sum_{i=1}^k(-1)^{k-i}f(i))

二项式反演:由(f(k)=sum_{i=0}^kC_k^ig(i)),二项式反演得(g(k)=sum_{i=0}^k(-1)^{k-i}f(i))

即答案(g(k)=sum_{i=1}^k(-1)^{k-i}i*(i-1)^{n-1})

//31ms	0KB
#include <cstdio>
#include <cctype>
#include <algorithm>
#define mod 1000000007
#define gc() getchar()
typedef long long LL;
const int N=2505;

int inv[N],C[N];

inline int read()
{
	int now=0,f=1;register char c=gc();
	for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
	for(;isdigit(c);now=now*10+c-48,c=gc());
	return now*f;
}
inline int FP(LL x,int k)
{
	LL res=1;
	for(; k; k>>=1,x=x*x%mod)
		if(k&1) res=res*x%mod;
	return res;
}

int main()
{
	const int n=read(),K=read();
	C[0]=inv[1]=1;
	for(int i=2; i<=K; ++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1; i<=K; ++i) C[i]=1ll*(K-i+1)*inv[i]%mod*C[i-1]%mod;

	LL res=0;
	for(int i=K,sgn=1; i; --i,sgn*=-1)
		res+=1ll*sgn*C[i]*i%mod*FP(i-1,n-1)%mod;
	printf("%lld
",(res%mod+mod)%mod);

	return 0;
}

原文地址:https://www.cnblogs.com/SovietPower/p/13686921.html