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\)范圍只有\(1\sim 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\n",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_i\geq p_i\),即\(x-p_i\geq s_i\)則可以轉移。
\(d_{i,j}\)\(i\)\(j\)距離。然后考慮一次拿起\(i+1\sim j\)的披薩去送,花的時間是\(d_{1,i}+\max_{k=i+1\sim 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+1\sim l}t_k+d_{i+1,i+1}+...+d_{l-1,l})-s_i\geq 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\n",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\n",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\n",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\n",(res%mod+mod)%mod);

	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM