2018 Benelux Algorithm Programming Contest 解题报告

A

温暖的签到题。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

const int N=1e5+7;

ll a[N],x;

int main(){
	int n=input();x=input();
	for(int i=1;i<=n;i++){
		a[i]=input();
	}

	sort(a+1,a+1+n);

	int Ans=1;

	for(int i=2;i<=n;i++){
		if(a[i]+a[i-1]<=x) Ans=i;
		else break;
	}

	printf("%d\n",Ans);
}

C

oeis题,oeis:A075777

import math

def cubestepdown(n):
    s1_0 = int(math.ceil(n ** (1 / 3.0)))
    minSA = -1
    s1 = s1_0
    while s1>=1:
        while n % s1 > 0:
            s1 = s1 - 1
        s1quot = int(n/s1)
        s2_0 = int(math.ceil(math.sqrt(n/s1)))
        s2 = s2_0
        while s2>=1:
            while s1quot % s2 > 0:
                s2 = s2 - 1
            s3 = int(n / (s1 * s2))
            SA = 2*(s1*s2 + s1*s3 + s2*s3)
            if minSA==-1:
                minSA = SA
            else:
                if SA<minSA:
                    minSA = SA
            s2 = s2 - 1
        s1 = s1 - 1
    return minSA

n = int(input())
print(cubestepdown(n))


J

数学题,婆罗摩笈多公式(Brahmagupta's formula):

\[A=\sqrt{(s-a)(s-b)(s-c)(s-d)} \\ 其中a、b、c、d为四边形的周长,s=\frac{a+b+c+d}{2} \]

带入公式即可算出最大面积。有关婆罗摩笈多公式的推导

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

double a,b,c,d,s;

int main(){
	a=input(),b=input(),c=input(),d=input();
	s=(a+b+c+d)/2;

	double Ans=sqrt((s-a)*(s-b)*(s-c)*(s-d));

	printf("%.12lf\n",Ans);
}

B

恶心模拟题,给每个日期排序,计算最大日期间隔,如果有相同需要分类讨论,如果当前日期小于10-28则计算与去年的10-28的距离,如果大于则计算与今年10-28的距离,选择与10-28距离最小的日期就是答案。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

#define PII pair <int,int>
#define fr first
#define sc second
#define mp make_pair

const int N=107;

PII a[N];
int day[]={0,31,28,31,30,31,30,31,31,30,31,30,31};

int cal(PII a,PII b){
	if(a>b) swap(a,b);
	if(a.fr==b.fr) return b.sc-a.sc;
	int res=day[a.fr]-a.sc+b.sc;
	for(int i=a.fr+1;i<b.fr;i++){
		res+=day[i];
	}
	return res;
}

PII getAns(PII a){
	PII res;
	if(a.sc-1==0){
		res.fr=a.fr-1;
		res.sc=day[a.fr-1];
	}else res.fr=a.fr,res.sc=a.sc-1;
	if(res.fr==0) res.fr=12,res.sc=31;
	return res;
}

int main(){
	int n=input();

	for(int i=1;i<=n;i++){
		a[i].fr=input(),a[i].sc=input();
	}

	sort(a+1,a+1+n);

	ll mx=0,pos;

	for(int i=1;i<=n;i++){
		if(i==1){
			if(mx<cal(mp(1,1),a[1])+cal(a[n],mp(12,31))+1){
				mx=cal(mp(1,1),a[1])+cal(a[n],mp(12,31))+1;
				pos=i;
			}
		}else if(mx<cal(a[i-1],a[i])){
			mx=cal(a[i-1],a[i]);
			pos=i;
		}else if(mx==cal(a[i-1],a[i])){
			PII t1=getAns(a[i]),t3=getAns(a[pos]),t2,t4;
			if(t1<mp(10,28)) t2=mp(1,1);
			else t2=mp(10,28);
			if(t3<mp(10,28)) t4=mp(1,1);
			else t4=mp(10,28);
			int tt1,tt2;
			if(t2==mp(1,1)) tt1=cal(mp(10,28),mp(1,1))+cal(t1,t2);
			else tt1=cal(t1,t2);
			if(t4==mp(1,1)) tt2=cal(mp(10,28),mp(1,1))+cal(t3,t4);
			else tt2=cal(t3,t4);
			if(tt1<tt2) pos=i;
		}
	}

	PII Ans=getAns(a[pos]);

	if(Ans.fr<10) printf("0%d-",Ans.fr);
	else printf("%d-",Ans.fr);
	if(Ans.sc<10) printf("0%d\n",Ans.sc);
	else printf("%d\n",Ans.sc);
}

F

二分答案,二分最小的天数计算总收益减去成本,剩下的钱跟\(m\)比较大小,判断二分方向,即可。但是不知道为啥我写c++总是爆ll,所以用pythonAC了。😂

n = 0
m = 0
p = []
c = []
def cal(day):
	res = 0
	for i in range(0, n):
		tmp = day*p[i] - c[i]
		if tmp > 0:
			res = res + tmp

	return res

n,m = map(int, input().split())

for i in range(0,n):
	pp = 0
	cc = 0
	pp,cc = map(int, input().split())
	p.append(pp)
	c.append(cc)

l = 1
r = int(2e9)
Ans = 0

while l <= r:
	mid = (l+r)//2
	if cal(mid) < m:
		l = mid+1
	else:
		Ans = mid
		r = mid -1

print(Ans)

K

构造题,首先连边的结论是把所有的叶子节点相连就能够满足题目条件。本题的用意是构造一个方案使得原来树上任意一个点都由两条路径到达,我们只需按dfs序中的叶子节点的顺序,前一半的节点跟后一半的节点相连就一定可以。

原理是对于同一子树的节点如果他们\(lca\)与其父亲节点的连边断开该子树就无法通过其它非子树内节点到达。所以要使任一子树中的叶子节点与是跟它是\(lca\)是根节点的点相连就能满足条件,那么\(dfs\)序上前一半的叶子结点和后一半的叶子节点相连一定会覆盖所有子树,因为不可能两个子树的叶子节点的和加起来超过总叶子节点数,故它们两个必定会相连。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

#define PII pair <int,int>
#define fr first
#define sc second
#define mp make_pair
#define pb push_back

const int N=1e5+7;

vector<int> G[N];
int cnt[N];
int n,h;
vector<int> DFS;
vector<PII> Ans;

void dfs(int u,int fa){
	if(cnt[u]==1) DFS.pb(u); 
	for(auto v:G[u]){
		if(v==fa) continue;
		dfs(v,u);
	}
}

int main(){
	n=input(),h=input();

	for(int i=1;i<n;i++){
		int u=input(),v=input();
		G[u].pb(v),G[v].pb(u);
		cnt[u]++,cnt[v]++;
	}

	dfs(0,-1);

	int t=DFS.size();

	for(int i=0;i<t/2;i++){
		Ans.pb(mp(DFS[i],DFS[i+t/2]));
	}
	if(t%2) Ans.pb(mp(DFS[t/2-1],DFS[t-1]));

	printf("%d\n",Ans.size());
	for(auto v:Ans){
		printf("%d %d\n",v.fr,v.sc);
	}
}

G

考虑"ABC"、"ACB"、"BAC"、"BCA"、"CAB"、"CBA",六种排列情况,我们就可以利用前缀和\(O(1)\)的计算出每种排列情况的答案。我们只需要枚举一开始有多少个连续的'A'或'B'或'C'就可以\(O(n)\)的解决这个问题。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

const int N=2e5+7;

int dp[3][N],n;
char s[N];

int cal(int k,int l,int r){
	int res=0;
	for(int i=0;i<3;i++){
		if(i==k) continue;
		if(l>r)
			res+=dp[i][r]+(dp[i][n]-dp[i][l-1]);
		else
			res+=(dp[i][r]-dp[i][l-1]);
	}
	return res;
}

int main(){
	n=input();

	scanf("%s",s+1);

	for(int i=1;i<=n;i++){
		dp[s[i]-'A'][i]++;
		for(int j=0;j<3;j++)
			dp[j][i]+=dp[j][i-1];
	}

	int cntA=dp[0][n],cntB=dp[1][n],cntC=dp[2][n],Ans=N;

	for(int i=0;i<=cntA;i++){
		int la,ra,lb,rb,lc,rc,tmp;

		ra=i;la=n-(cntA-i)+1;
		lb=ra+1,rb=ra+cntB;
		lc=rb+1,rc=rb+cntC;

		tmp=0;
		if(cntA)tmp+=cal(0,la,ra);
		if(cntB)tmp+=cal(1,lb,rb);
		if(cntC)tmp+=cal(2,lc,rc);
		Ans=min(Ans,tmp);

		ra=i;la=n-(cntA-i)+1;
		lc=ra+1,rc=ra+cntC;
		lb=rc+1,rb=rc+cntB;

		tmp=0;
		if(cntA)tmp+=cal(0,la,ra);
		if(cntB)tmp+=cal(1,lb,rb);
		if(cntC)tmp+=cal(2,lc,rc);
		Ans=min(Ans,tmp);
	}

	// cout<<Ans<<endl;

	for(int i=0;i<=cntB;i++){
		int la,ra,lb,rb,lc,rc,tmp;

		rb=i;lb=n-(cntB-i)+1;
		la=rb+1,ra=rb+cntA;
		lc=ra+1,rc=ra+cntC;

		tmp=0;
		if(cntA)tmp+=cal(0,la,ra);
		if(cntB)tmp+=cal(1,lb,rb);
		if(cntC)tmp+=cal(2,lc,rc);
		Ans=min(Ans,tmp);

		rb=i;lb=n-(cntB-i)+1;
		lc=rb+1,rc=rb+cntC;
		la=rc+1,ra=rc+cntA;

		tmp=0;
		if(cntA)tmp+=cal(0,la,ra);
		if(cntB)tmp+=cal(1,lb,rb);
		if(cntC)tmp+=cal(2,lc,rc);
		Ans=min(Ans,tmp);
	}

	for(int i=0;i<=cntC;i++){
		int la,ra,lb,rb,lc,rc,tmp;

		rc=i;lc=n-(cntC-i)+1;
		la=rc+1,ra=rc+cntA;
		lb=ra+1,rb=ra+cntB;

		tmp=0;
		if(cntA)tmp+=cal(0,la,ra);
		if(cntB)tmp+=cal(1,lb,rb);
		if(cntC)tmp+=cal(2,lc,rc);
		Ans=min(Ans,tmp);

		rc=i;lc=n-(cntC-i)+1;
		lb=rc+1,rb=rc+cntB;
		la=rb+1,ra=rb+cntA;

		tmp=0;
		if(cntA)tmp+=cal(0,la,ra);
		if(cntB)tmp+=cal(1,lb,rb);
		if(cntC)tmp+=cal(2,lc,rc);
		Ans=min(Ans,tmp);
	}

	// cout<<Ans<<endl;

	printf("%d\n",Ans);
}

I

二分答案套最大流。首先观察到避难所\(s\)的数量很少,我们考虑可以通过最短路算法把整张图变为二分图,这样就避免了在原图上跑网络流导致超时。接着我们二分最大边权,按照边权构造网络(即连接所有边权小于等于二分出最大边权的边)流量设为正无穷,源点连接所有的点流量上限为每个点的人口,所有的汇点连接所有避难所,表示避难所的承载容量。如果最大流==总人口那么这个时间是可行的,我们可以缩小最大时间,反之则该时间不可行,需要放大最大时间。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

const ll N=2e5+150;
#define PII pair <ll,ll> 
#define fr first
#define sc second
#define mp make_pair
#define clr(a,b) memset(a,b,sizeof a)
const ll maxn=2e5+150;
const ll INF=1ll<<60;
struct Edge
{
    int from,to;
    ll cap,flow;
};
struct Dinic
{
    int n,m,s,t;
    vector<Edge>edges;
    vector<int>G[maxn];
    bool vis[maxn];
    int d[maxn],cur[maxn];
    void init(int n)
    {
        this->n=n;
        for(int i=0;i<n;i++)
        G[i].clear();
        edges.clear();
    }
    void AddEdge(int from,int to,ll cap)
    {
        edges.push_back((Edge){from,to,cap,0});
        edges.push_back((Edge){to,from,0,0});
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    bool bfs()
    {
        memset(vis,0,sizeof(vis));
        queue<int>Q;
        Q.push(s);
        d[s]=0;
        vis[s]=1;
        while(!Q.empty())
        {
            int x=Q.front();Q.pop();
            for(int i=0;i<G[x].size();i++)
            {
                Edge& e=edges[G[x][i]];
                if(!vis[e.to]&&e.cap>e.flow)
                {
                    vis[e.to]=1;
                    d[e.to]=d[x]+1;
                    Q.push(e.to);
                }
            }
        }
        return vis[t];
    }
    ll dfs(int x,ll a)
    {
        if(x==t||a==0)return a;
        ll flow=0,f;
        for(int& i=cur[x];i<G[x].size();i++)
        {
            Edge& e=edges[G[x][i]];
            if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.cap-e.flow)))>0)
            {
                e.flow+=f;
                edges[G[x][i]^1].flow-=f;
                flow+=f;
                a-=f;
                if(a==0)break;
            }
        }
        return flow;
    }
    ll Maxflow(int s,int t)
    {
        this->s=s,this->t=t;
        ll flow=0;
        while(bfs())
        {
            memset(cur,0,sizeof(cur));
            flow+=dfs(s,INF);
        }
        return flow;
    }
}A;

struct edge{
	ll v,w,next;	
}e[5*N];
int head[N],Cnt=0;
ll n,m,s;
ll p[N],sp;
ll st[20],c[20];
ll dis[20][N];
struct node{
	ll u,d;
	bool operator <(const node& rhs) const {return d>rhs.d;}
};

void Ins(int u,int v,ll w){
	e[++Cnt]=(edge){v,w,head[u]},head[u]=Cnt;
	e[++Cnt]=(edge){u,w,head[v]},head[v]=Cnt;
}

priority_queue <node> Q;

void Dijkstra(ll k,ll sta){
	clr(dis[k],0x3f);
	dis[k][sta]=0;
	Q.push((node){sta,0});
	while(!Q.empty()){
		node fr=Q.top(); Q.pop();
		ll u=fr.u,d=fr.d,v,w;
		if(d!=dis[k][u]) continue;
		for(ll i=head[u];i;i=e[i].next){
			if(dis[k][u]+(w=e[i].w)<dis[k][v=e[i].v])
				dis[k][v]=dis[k][u]+w,Q.push((node){v,dis[k][v]});
		}
	}
}

ll B[maxn*20],tot=0;
ll tmp[N];

bool work(ll mid){
	A.init(n+s+1);
	for(ll i=1;i<=n;i++) tmp[i]=0;
	for(ll i=1;i<=n;i++){
		for(ll j=1;j<=s;j++){
			if(dis[j][i]<=mid)
				A.AddEdge(i,j+n,INF),tmp[i]+=c[j];
		}
	}

	for(ll i=1;i<=n;i++){
		if(tmp[i]<p[i]) return 0;
		A.AddEdge(0,i,p[i]);
	}

	for(ll i=1;i<=s;i++){
		A.AddEdge(n+i,n+s+1,c[i]);
	}
	return A.Maxflow(0,n+s+1)==sp;
}

int main(){
	n=input(),m=input(),s=input();

	for(ll i=1;i<=n;i++) p[i]=input(),sp+=p[i];

	for(ll i=1;i<=m;i++){
		ll u=input(),v=input(),w=input();
		Ins(u,v,w);
	}

	for(ll i=1;i<=s;i++){
		st[i]=input(),c[i]=input();
	}

	for(ll i=1;i<=s;i++) Dijkstra(i,st[i]);

	// for(ll i=1;i<=s;i++){
 //    	for(ll j=1;j<=n;j++) cout<<dis[i][j]<<" ";
 //    	cout<<endl;
	// }

	for(ll i=1;i<=s;i++)
    	for(ll j=1;j<=n;j++) B[++tot]=dis[i][j];
    sort(B+1,B+tot+1);
    tot=unique(B+1,B+tot+1)-(B+1);

    // for(int i=1;i<=tot;i++) cout<<B[i]<<" ";cout<<endl;

	ll l=1,r=tot,Ans;

	while(l<=r){
		ll mid=(l+r)>>1;
		// cout<<mid<<" "<<work(mid)<<endl;
		if(!work(B[mid])) l=mid+1;
		else Ans=B[mid],r=mid-1; 
	}

	printf("%lld\n",Ans);
}

E

首先我们考虑反过来来计算答案,我们求有多少个序列,其中至少包含有\(1\)个有序元素,然后用总排列数减去我们计算的数,就是答案。

对于第\(i\)个位置我们应该计算\(dp[i-1]*p'[n-i]\),其中\(dp[i]\)表示前\(i\)个元素完全乱序的方案数,\(p'[i]\)表示后\(i\)个随便放的方案数(当然为啥不用\(p\)而使用\(p'\)是考虑到如果有相同的元素需要除掉相同的数的个数的阶乘,其排列是对答案无贡献的)。然后记忆化搜索搞一搞就可以了。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

const int N=5007;
const ll mod=1e9+9;

ll dp[N],inv[N],p[N];
ll a[N];

ll powmod(ll a,ll b){
	ll res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

void init(){
	p[0]=1,inv[0]=1;
	for(int i=1;i<N;i++){
		p[i]=p[i-1]*i%mod;
		inv[i]=powmod(p[i],mod-2);
	}
}

ll dfs(int x){
	if(a[1]==a[x]) return 0;
	if(dp[x]) return dp[x];

	ll sum=0,tmp=1;

	for(int i=x;i>=1;i--){
		if(a[i]!=a[1]){
			int cnt=1;
			while(a[i]==a[i-1]){
				sum=(sum+dfs(i-1)*p[x-i]%mod*tmp%mod*inv[cnt-1]%mod+mod)%mod;
				cnt++,i--;
			}
			sum=(sum+dfs(i-1)*p[x-i]%mod*tmp%mod*inv[cnt-1]%mod+mod)%mod;
			tmp=tmp*inv[cnt]%mod;
		}else{
			sum=(sum+p[x-1]*tmp%mod*inv[i-1]%mod+mod)%mod;
			tmp=tmp*inv[i]%mod;
			break;
		}
	}
	return dp[x]=(p[x]*tmp%mod-sum+mod)%mod;
}

int main(){
	init();
	int n=input();
	for(int i=1;i<=n;i++) a[i]=input();

	sort(a+1,a+1+n);
	
	printf("%lld\n",dfs(n));
	
}
posted @ 2020-07-02 22:43  _aether  阅读(218)  评论(0编辑  收藏  举报