2019 BUPT Winter Training #1 div.1

A - Alice the Fan

思路: 记忆化搜索
根据规则记忆化搜索即可…

#include <cstdio>
#include <algorithm>
#include <map>
using namespace std;
//(25):(0~23),(0~23):25,(x+2):(x),(x):(x+2)
//25:23 69,(21)
bool can[12][205][205]={0},fid[7][12][205][205]={0};
int mem[6][2];
int fan[12][205][205][6][2],shengfu[12][205][205][2];
void dfs(int t,int d,int a,int b,int x,int y){
	if(t>6||a>200||b>200||can[d][a][b])return ;
	if(x==3||y==3){
		can[d][a][b]=1;
		for(int i=1;i<=5;i++){
			fan[d][a][b][i][0]=mem[i][0];
			fan[d][a][b][i][1]=mem[i][1];
		}
		shengfu[d][a][b][0]=x;
		shengfu[d][a][b][1]=y;
		return ;
	}
	if(t>5||fid[t][d][a][b])return ;
	if(t<=4){
		for(int i=0;i<=23;i++){
			mem[t][0]=i;mem[t][1]=25;
			dfs(t+1,d-1,a+i,b+25,x,y+1);
			mem[t][0]=25;mem[t][1]=i;
			dfs(t+1,d+1,a+25,b+i,x+1,y);
		}
		for(int i=24;i<=198;i++){
			mem[t][0]=i;mem[t][1]=i+2;
			dfs(t+1,d-1,a+i,b+i+2,x,y+1);
			mem[t][0]=i+2;mem[t][1]=i;
			dfs(t+1,d+1,a+i+2,b+i,x+1,y);
		}
	}
	if(t==5){
		for(int i=0;i<=13;i++){
			mem[t][0]=i;mem[t][1]=15;
			dfs(t+1,d-1,a+i,b+15,x,y+1);
			mem[t][0]=15;mem[t][1]=i;
			dfs(t+1,d+1,a+15,b+i,x+1,y);
		}
		for(int i=14;i<=198;i++){
			mem[t][0]=i;mem[t][1]=i+2;
			dfs(t+1,d-1,a+i,b+i+2,x,y+1);
			mem[t][0]=i+2;mem[t][1]=i;
			dfs(t+1,d+1,a+i+2,b+i,x+1,y);
		}
	}
	fid[t][d][a][b]=1;
	return ;
}
int main(){
	int n;
	dfs(1,5,0,0,0,0);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		bool pos=0;
		//for(int i=3;i>=0;i--){
		for(int i=3;i>=-3;i--){
			if(can[i+5][a][b]){
				pos=1;
				printf("%d:%d\n",shengfu[i+5][a][b][0],shengfu[i+5][a][b][1]);
				int q=shengfu[i+5][a][b][0]+shengfu[i+5][a][b][1];
				for(int j=1;j<=q;j++){
					printf("%d:%d%s",fan[i+5][a][b][j][0],fan[i+5][a][b][j][1],(j==q)?"\n":" ");
				}
				break;
			}
		}
		if(!pos){
			puts("Impossible");
		}
	}
}

B - Barber Shop

思路: 公式+暴力
设一共有 k k k个分量 P 1 , P 2 , … , P k P_1,P_2,\dots,P_k P1,P2,,Pk,那么:
r e s = ( n ∣ P 1 ∣   ∣ P 2 ∣   …   ∣ P k ∣ ) ∏ i = 1 k g ( ∣ P k ∣ + 1 ) . res=\binom{n}{|P_1|\ |P_2|\ \dots\ |P_k|}\prod_{i=1}^k g(|P_k|+1). res=(P1 P2  Pkn)i=1kg(Pk+1).
其中 g ( n ) = n n − 2 g(n)=n^{n-2} g(n)=nn2.
前面一个系数很好求出,是 n n n个不同的位置分配给 k k k个分量产生的.
后面一个如何理解?
想象一个序列 a [ n ] a[n] a[n],值域是 [ 1 , n ] [1,n] [1,n],元素 i i i的个数不超过 i i i个.因此 1 ∼ n 1\sim n 1n的个数被看作是 y ⩽ x y\leqslant x yx的格子不同的走法的个数染色,我们从 ( 0 , 0 ) (0,0) (0,0)走到 ( n , n ) (n,n) (n,n),因此一共有 ( n + 1 ) n − 1 (n+1)^{n-1} (n+1)n1种不同的走法.

#include <cstdio>
const int N=200005;
const long long mod=1e9+7;
long long qp(long long a,long long b){
	long long res=1;
	while(b){
		if(b&1)res=res*a%mod;
		a=a*a%mod; b>>=1;
	}
	return res;
}
int ans[N],a[N];
long long res,fac[N+5],inv[N+5];
long long CN(int *ans,int n){
	long long res=1; for(int i=1;i<=n;i++)res=res*inv[ans[i]]%mod;
	return res;
}
long long G(int *ans,int n){
	long long res=1; for(int i=1;i<=n;i++)res=res*qp(ans[i]+1,ans[i]-1)%mod;
	return res;
}
int main(){
	int T;
	fac[0]=1;
	for(int i=1;i<=N;i++)fac[i]=fac[i-1]*i%mod;
	inv[N]=qp(fac[N],mod-2);
	for(int i=N;i>=2;i--)inv[i-1]=inv[i]*i%mod;
	inv[0]=1;
	scanf("%d",&T);
	while(T--){
		res=1;
		int n,c=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)scanf("%d",&a[i]);
		a[n+1]=0;
		for(int i=1;i<=n;i++){
			if(a[i]){
				ans[++c]=0;
				for(int suc=i,j;a[suc];j=a[suc],a[suc]=0,suc=j)ans[c]++;
			}
		}
		printf("%I64d%s",(fac[n]*CN(ans,c)%mod)*G(ans,c)%mod,T?"\n":"");
	}
}

C - Chef Counts Semi-BSTs

思路: 还没想出来
递推式:
f [ i ] = { ∑ i = 1 n − 1 i i − 1 ⋅ f [ n − 1 − i ] , i ⩾ 2 ; 1 , i = 1. f[i]=\left\{\begin{array}{ll} \displaystyle \sum_{i=1}^{n-1} i^{i-1}\cdot f[n-1-i]&amp;,i\geqslant 2;\\ 1&amp;,i=1. \end{array}\right. f[i]=i=1n1ii1f[n1i]1,i2;,i=1.

D - Mst 最小生成树

思路: 网络流
我们不妨加一条不在树上的边 x x x,并设形成了环 { e , e x 1 , e x 2 , … , e x k ( x ) } \{e,e_{x_1},e_{x_2},\dots,e_{x_{k(x)}}\} {e,ex1,ex2,,exk(x)}.
显然必须满足下列不等式
w ( e x i ) ⩽ w ( x ) , i = 1 , 2 , … , k ( x ) w(e_{x_i})\leqslant w(x),i=1,2,\dots,k(x) w(exi)w(x),i=1,2,,k(x)
故,设正的变化函数 Δ w = ∣ w ′ − w ∣ \Delta w=|w&#x27;-w| Δw=ww:
∀ e ̸ ∈ G − T , Δ w ( e e i ) + Δ w ( e ) ⩾ w ( e x i ) − w ( e ) ⩾ 0 \forall e\not \in G-T,\Delta w(e_{e_i})+\Delta w(e)\geqslant w(e_{x_i})-w(e)\geqslant 0 e̸GT,Δw(eei)+Δw(e)w(exi)w(e)0
用最大费用最大流跑一遍即可…注意选择正确的模板…我zkw模板是错的,mcmf模板是对的…

#include <cstdio>
#include <queue>
#include <map>
#include <algorithm>
#include <cstring>
using namespace std;
//typedef long long ll;
typedef int flowType;
//const ll INF=0x3f3f3f3f3f3f3f3fLL;
const int INF=0x3f3f3f3f;
const int N=850,M=1000000;
const int Headsize=N,Edgesize=M;
int head[Headsize+5],mal;
struct edge{
    int nx,to;flowType w,c;
}e[Edgesize+5];
inline void init(){
    mal=0;
    memset(head,-1,sizeof(head));
}
inline void _addedge(int u,int v,flowType val,flowType cost){
    e[mal].to=v;e[mal].c=cost;e[mal].w=val;e[mal].nx=head[u];head[u]=mal++;
}
inline void addedge(int u,int v,flowType val,flowType cost){
    _addedge(u,v,val,cost);_addedge(v,u,0,-cost);
}

struct MCMF{
    bool vis[N];flowType dis[N],lnk[N];
    int n,s,t;
    inline void set(int _n,int _s,int _t){n=_n;s=_s;t=_t;}
    bool spfa(){
        memset(lnk, -1,sizeof(lnk));
        memset(dis,192,sizeof(dis));
        queue<int> Q;
        Q.push(s);
        dis[s]=0;vis[s]=1;
        while(Q.size()){
            int now=Q.front();Q.pop();
            vis[now]=0;
            for(int i=head[now];i+1;i=e[i].nx){
                if(e[i].w&&dis[e[i].to]<dis[now]+e[i].c){
                    dis[e[i].to]=dis[now]+e[i].c;
                    lnk[e[i].to]=i;
                    if(!vis[e[i].to]){
                        vis[e[i].to]=1;
                        Q.push(e[i].to);
                    }
                }
            }
        }
        return dis[t]^dis[0];
    }
    int mcmf(int n,int s,int t){
        set(n,s,t);
        int mincost=0;
        while(spfa()&&dis[t]>0){
            int mw=INF;
            for(int i=lnk[t];i+1;i=lnk[e[i^1].to])mw=min(mw,e[i].w);
            for(int i=lnk[t];i+1;i=lnk[e[i^1].to])e[i].w-=mw,e[i^1].w+=mw;
            mincost+=dis[t]*mw;
        }
        return mincost;
    }
}netFlow;
int G[55][55],faz[55],dep[55];
int sig[55][55];
int n,m;
void dfs(int u,int f){
    for(int v=1;v<=n;v++){
        if(v==f)continue;
        if(sig[u][v]){
            dep[v]=dep[u]+1;
            faz[v]=u;
            dfs(v,u);
        }
    }
}
void cret(int x,int y,int w,int id){
    if(x==y)return ;
    if(dep[x]<dep[y])swap(x,y);
    if(G[x][faz[x]]>w){
        addedge(sig[x][faz[x]],id,1,G[x][faz[x]]-w);
    }
    cret(faz[x],y,w,id);
}
int main(){
    dep[1]=0;
    init();
    scanf("%d%d",&n,&m);
    int u,v,w;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        G[u][v]=G[v][u]=w;
    }
    int qwq=n,s=1,t=m+2;
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        sig[u][v]=sig[v][u]=i+1;
        addedge(s,i+1,1,0);
    }
    dfs(1,0);
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++){
            if(!sig[i][j]&&G[i][j]){
                cret(i,j,G[i][j],++qwq);
                addedge(qwq,t,1,0);
            }
        }
    }
    printf("%d\n",netFlow.mcmf(m+2,s,t));
}

E - King Kog’s Reception

思路: 线段树
考虑到若一个节点被影响当且仅当左侧链的最右侧覆盖了这个节点.
最右侧的计算有两个因素,一个是当前节点被影响的最右侧的值,一个是当前节点往后一长串覆盖的节点的工作时间之和.
这样就可以做了.
再考虑一个规约简化代码量:
i i i个骑士如果不在,那么我们设立空骑士这个骑士被影响的最右侧的值为 m x = i mx=i mx=i,这个骑士需要面见国王的时间为 s u m = 0 sum=0 sum=0.

#include <cstdio>
#include <algorithm>
using namespace std;
const int N=1e6+5;
long long mx[N<<2],sum[N<<2];//,left[N<<2];
void pushup(int p){
	sum[p]=sum[p<<1]+sum[p<<1|1];
	mx[p]=max(mx[p<<1]+sum[p<<1|1],mx[p<<1|1]);
	// if(left[p<<1]!=-1)left[p]=left[p<<1];
	// else left[p]=left[p<<1|1];
	// if(mx[p<<1]!=-1&&mx[p<<1]>=left[p<<1|1]){
	// 	mx[p]=left[p]+sum[p];
	// }else{
	// 	mx[p]=mx[p<<1|1];
	// }
}
long long b[N];
void build(int p,int l,int r){
	if(l==r){
		//left[p]=a[l];
		sum[p]=b[l];
		mx[p]=l+b[l];
		//mx[p]=(a[l]==-1)?-1:(a[l]+b[l]);
		return ;
	}
	int m=(l+r)>>1;
	build(p<<1,l,m);
	build(p<<1|1,m+1,r);
	pushup(p);
}

void update(int p,int l,int r,int i){
	if(l==r){
		//left[p]=a[l];
		sum[p]=b[l];
		mx[p]=l+b[l];
		//mx[p]=-1;
		return ;
	}
	int m=(l+r)>>1;
	if(i<=m)update(p<<1,l,m,i);
	else update(p<<1|1,m+1,r,i);
	pushup(p);
}
// void join(int p,int l,int r,int i){
// 	if(l==r){
// 		sum[p]=b[l];
// 		mx[p]=a[l]+b[l];
// 		// left[p]=a[l];
// 		// sum[p]=b[l];
// 		// mx[p]=a[l]+b[l];
// 		return ;
// 	}
// 	int m=(l+r)>>1;
// 	if(i<=m)join(p<<1,l,m,i);
// 	else join(p<<1|1,m+1,r,i);
// 	pushup(p);
// }
// int query(int p,int l,int r,int c,int offset){
// 	if(l==r){
// 		return (a[l]==-1)?0:b[l];
// 	}
// 	int m=(l+r)>>1;
// 	if(c<=m)return query(p<<1,l,m,c,offset);
// 	if(mx[p<<1]==-1)return query(p<<1,m+1,r,c,offset);
// 	if(left[p<<1|1]==-1)return (mx[p<<1]<=c)?0:(mx[p<<1]-c);
// 	if(mx[p<<1]>=left[p<<1|1])return
// }
long long res;
void query(int p,int l,int r,int c){
	if(r<=c){
		res=max(mx[p],res+sum[p]);
		return ;
	}
	int m=(l+r)>>1;
	query(p<<1,l,m,c);
	if(m<c)query(p<<1|1,m+1,r,c);
	return ;
}
char o[10];
int Time[N+5];
int main(){
	int Q;
	scanf("%d",&Q);
	for(int i=1;i<=1000000;i++){
		b[i]=0;
	}
	build(1,1,1000000);
	for(int i=1;i<=Q;i++){
		scanf("%s",o);
		switch(o[0]){
			case '+':{
				int d;
				scanf("%d%d",&Time[i],&d);
				b[Time[i]]=d;
				update(1,1,1000000,Time[i]);
				break;
			}
			case '-':{
				scanf("%d",&Time[i]);
				b[Time[Time[i]]]=0;
				update(1,1,1000000,Time[Time[i]]);
				break;
			}
			case '?':{
				scanf("%d",&Time[i]);
				res=-1;
				query(1,1,1000000,Time[i]);
				printf("%I64d\n",res-Time[i]);
				break;
			}
		}
	}
}

F - Bi-shoe and Phi-shoe

思路: 任意积性筛法+递推
今天最简单的没想到是个数论题…
显然用埃氏筛就可以 O ( n l g l g n ) O(nlglgn) O(nlglgn)筛出 φ \varphi φ,每个人各自的答案就是 r e s [ i ] = m i n φ − 1 ( i ) res[i]=min \varphi^{-1}(i) res[i]=minφ1(i).

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=1000050;
int res[1000050+5];
int phi[N+5];
void sieve(){
	memset(res,0x3f,sizeof(res));
	for(int i=1;i<=N;i++)phi[i]=i;
	phi[1]=0;
	for(int i=2;i<=N;i++){
		if(phi[i]==i){
			for(int j=i+i;j<=N;j+=i){
				phi[j]=phi[j]/i*(i-1);
			}
			phi[i]=i-1;
		}
	}
	for(int i=1;i<=N;i++){
		res[phi[i]]=min(res[phi[i]],i);
	}
	for(int i=N-1;i>=1;i--){
		res[i]=min(res[i],res[i+1]);
	}
}
int main(){
	sieve();
	//printf("%d",(int)(2e10));
	//printf("%d",phi[6]);
	int T;
	scanf("%d",&T);
	int n;
	for(int ttt=1;ttt<=T;ttt++){
		scanf("%d",&n);
		int a;
		long long ans=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&a);
			ans+=res[a];
			//printf("%d ",res[a]);
		}
		printf("Case %d: %lld Xukha\n",ttt,ans);
	}
}

G - Archi and Tree

H - A Race Against Time

思路: 动态规划
g ( i , j ) = ∣ H i − H j ∣ − i + j + d p [ j ] + P j , g ( i , M a d i s o n ) = n + 1 − i g(i,j)=|H_i-H_j|-i+j+dp[j]+P_j,g(i,Madison)=n+1-i g(i,j)=HiHji+j+dp[j]+Pj,g(i,Madison)=n+1i.
d p [ i ] = min ⁡ t i l l   h [ j ] &gt; h [ i ] { g ( i , j ) } = H i − i + min ⁡ b e f o r e   h [ j ] &gt; h [ i ]   a n d   h [ R ] &gt; h [ i ] { d p [ j ] + C j , g ( i , R ) } dp[i]=\min_{till\ h[j]&gt;h[i]}\{g(i,j)\}=H_i-i+\min_{before\ h[j]&gt;h[i]\ and\ h[R]&gt;h[i]}\{dp[j]+C_j,g(i,R)\} dp[i]=till h[j]>h[i]min{g(i,j)}=Hii+before h[j]>h[i] and h[R]>h[i]min{dp[j]+Cj,g(i,R)}
这里操作是倒着来的.

#include <cstdio>
#include <set>
#include <utility>
#include <algorithm>
using namespace std;
const int N=1e5+50;
long long h[N],sgt[N<<2],c[N],dp[N];
struct flow{
	pair<int,int> S[N];
	int top;
	flow(){top=0;}
	flow(pair<int,int> s){top=1;S[top]=s;}
	int pushtill(pair<int,int> s){
		while(S[top].first<=s.first)top--;
		int t=S[top].second;
		S[++top]=s;
		return t;
	}
};
void build(int p,int l,int r){
    if(l==r){
        sgt[p]=dp[l]+c[l];
        return ;
    }
    int m=(l+r)>>1;
    build(p<<1,l,m);
    build(p<<1|1,m+1,r);
    sgt[p]=min(sgt[p<<1],sgt[p<<1|1]);
}
void update(int p,int l,int r,int a){
    if(l==r){
        sgt[p]=dp[l]+c[l];
        return ;
    }
    int m=(l+r)>>1;
    if(a<=m)update(p<<1,l,m,a);
    else update(p<<1|1,m+1,r,a);
    sgt[p]=min(sgt[p<<1],sgt[p<<1|1]);
}
long long query(int p,int l,int r,int L,int R){
    if(L>R)return 0x3f3f3f3f3f3f3fLL;
    if(L<=l&&r<=R){
        return sgt[p];
    }
    int m=(l+r)>>1;
    if(R<=m)return query(p<<1,l,m,L,R);
    if(m< L)return query(p<<1|1,m+1,r,L,R);
    return min(query(p<<1,l,m,L,R),query(p<<1|1,m+1,r,L,R));
}
int main(){
    int n;
    scanf("%d",&n);
    long long p;
    for(int i=1;i<=n;i++){
        scanf("%lld",&h[i]);
    }
    c[1]=0+1-h[1];
    for(int i=2;i<=n;i++){
        scanf("%lld",&p);
        c[i]=p+i-h[i];
    }
    n++;
    dp[n]=0;
    build(1,1,n);
    flow S({1e9+7,n});
    for(int i=n-1;i>=1;i--){
        pair<int,int> s={h[i],i};
        int L=i+1,R=S.pushtill(s);
        //printf("[%d,%d]",L,R);
        if(R==n){
            dp[i]=min(h[i]+query(1,1,n,L,R-1),(long long)n)-i;
        }else{
            dp[i]=h[i]-i+min(query(1,1,n,L,R-1),c[R]+2*(h[R]-h[i])+dp[R]);
        }
        update(1,1,n,i);
        //printf("%d,%lld\n",i,dp[i]);
    }
    printf("%lld",dp[1]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值