HEOI2017题解

Day 1 :

T1 : 期末考试

很水的一道题,但是自己搞了大半天过不了大样例.
最后还A了...
主要思想就是枚举最后一个完成的任务的时间
然后对两部分的代价分类讨论统计一下。
(考试代码,略丑)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
	x=0;char ch;bool flag = false;
	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
inline void read(ll &x){
	x=0;char ch;bool flag = false;
	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 100010;
ll A,B,C;int n,m;
int t[maxn],b[maxn];
namespace work1{
	int maxtim = 0;
#define lowbit(x) ((x)&(-x))
	struct Bit{
		ll c[maxn];
		inline void modify(int x,int val){
			for(;x <= maxtim;x += lowbit(x))
			c[x] += val;
		}
		inline ll query(int x){
			ll ret = 0;
			for(;x;x-=lowbit(x)) ret += c[x];
			return ret;
		}
	}sit,sut,sib,sub;
	int main(){
		ll sum_t = 0,sum_b = 0;
		rep(i,1,n){
			read(t[i]);
			maxtim = max(maxtim,t[i]);
			sum_t += t[i];
		}
		rep(i,1,m){
			read(b[i]);
			maxtim = max(maxtim,b[i]);
			sum_b += b[i];
		}
		rep(i,1,n) sit.modify(t[i],1),sut.modify(t[i],t[i]);
		rep(i,1,m) sib.modify(b[i],1),sub.modify(b[i],b[i]);
		ll ans = (1LL<<60);
		ll res,lsum_t = 0,rsum_t = sum_t,lsum_b = 0,rsum_b = sum_b;
		int lsiz_t = 0,rsiz_t = n,lsiz_b = 0,rsiz_b = m;
		rep(i,1,maxtim){
			lsiz_t = sit.query(i);rsiz_t = n - lsiz_t;
			lsum_t = sut.query(i);rsum_t = sum_t - lsum_t;
			lsiz_b = sib.query(i);rsiz_b = m - lsiz_b;
			lsum_b = sub.query(i);rsum_b = sum_b - lsum_b;
			res = C*(1LL*i*lsiz_t - lsum_t);
			ll lc = 1LL*i*lsiz_b - lsum_b;
			ll rc = rsum_b - 1LL*i*rsiz_b;
			if(B <= A){
				res += rc*B;
			}else{
				if(rc >= lc){
					res += lc*A;
					rc -= lc;
				}else{
					res += rc*A;
					rc = 0;
				}
				if(rc != 0){
					res += rc*B;
					rc = 0;
				}
			}
			ans = min(ans,res);
		}
		printf("%lld
",ans);
		return 0;
	}
}
namespace work2{
	int main(){
		int tim = 0;
		rep(i,1,n) read(t[i]);
		rep(i,1,m){
			read(b[i]);
			tim = max(b[i],tim);
		}
		ll ans = 0;
		rep(i,1,n){
			if(t[i] >= tim) continue;
			ans += (tim - t[i])*C;
		}
		printf("%lld
",ans);
		return 0;
	}
}
namespace work3{
	int main(){
		int minn = 1000000;
		rep(i,1,n){
			read(t[i]);
			minn = min(minn,t[i]);
		}
		ll can = 0,nd = 0;
		rep(i,1,m){
			read(b[i]);
			if(b[i] < minn) can += minn - b[i];
			if(b[i] > minn) nd += b[i] - minn;
		}
		ll res = 0;
		if(B <= A) res += nd*B;
		else{
			if(can >= nd){
				res += nd*A;
				nd = 0;
			}else{
				res += can*A;
				nd -= can;
			}
			if(nd != 0) res += nd*B;
		}
		printf("%lld
",res);
		return 0;
	}
}
int main(){
	read(A);read(B);read(C);
	read(n);read(m);
	if(A == 1000000000 && B == 1000000000) work2::main();
	else if(C == 10000000000000000LL) work3::main();
	else work1::main();
	return 0;
}

T2 : 相逢是问候

我们发现每个位置上的值在修改操作中是作为幂出现的.
所以我们不能直接将序列 % p进行维护.
考虑欧拉定理.那么应该 % 上的数应该是phi(p)
或者phi(phi(p))或phi(phi(phi(p)))什么的.
打表观察规律后可以发现一个数最多被phi() log次就会变成1.

所以预处理出来不断phi(p)得到的所有不为1的数.
可以通过维护每个点被修改操作覆盖了多少次来剪枝.
对于最小的被覆盖次数也大于得到的区间就可以直接跳出了.
所以可以用线段树实现这个过程.
复杂度是... (O(nlog^3 n)) !??!?!?!?
不要问我是怎么跑过去的.

UPD : 在bzoj 上听闻管理员把我叉掉了...
然后swm_sxt也说我的代码被叉掉了...
毕姥爷的题解这么说的啊...我是照着题解写的啊...
然后上知乎得知毕姥爷被叉掉了.
... ...
代码已更正.可以过掉目前bzoj上的数据.
(其实就多了一句 ph[++cntp] = 1 )...QAQ

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
	x=0;static char ch;bool flag = false;
	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 50010;
inline int phi(int x){
	int ret = x;
	for(rg i=2;i*i<=x;++i){
		if(x % i == 0){
			ret /= i;ret *= i-1;
			while(x % i == 0) x /= i;
		}
	}
	if(x ^ 1) ret /= x,ret *= x-1;
	return ret;
}
int ph[maxn],cntp;
int n,m,p,c;
int a[maxn];
struct Node{
	int val,mn;
	Node(){val = mn = 0;}
	friend Node operator + (const Node &a,const Node &b){
		Node c;
		c.val = (a.val + b.val) % ph[0];
		c.mn = min(a.mn,b.mn);
		return c;
	}
}T[maxn<<2];
int query(int rt,int l,int r,int L,int R){
	if(L <= l && r <= R) return T[rt].val;
	int mid = l+r >> 1;
	if(R <= mid) return query(rt<<1,l,mid,L,R);
	if(L >  mid) return query(rt<<1|1,mid+1,r,L,R);
	return (query(rt<<1,l,mid,L,R) + query(rt<<1|1,mid+1,r,L,R)) % ph[0];
}
inline int qpow(int x,int p,int mod){
	int ret = 1;
	for(;p;p>>=1,x=1LL*x*x%mod) if(p&1) ret=1LL*ret*x%mod;
	return ret;
}
inline int calc(int x,int t){
	int ret = x;
	if(ret >= ph[t]) ret = ret % ph[t] + ph[t];
	per(i,t,1){
		x = ret;
		ret = qpow(c,x,ph[i-1]);
		if(x && !ret) ret += ph[i-1];
	}
	return ret % ph[0];
}
void build(int rt,int l,int r){
	if(l == r){
		T[rt].val = calc(a[l],0);
		return ;
	}
	int mid = l+r >> 1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	T[rt] = T[rt<<1] + T[rt<<1|1];
}
void modify(int rt,int l,int r,int L,int R){
	if(T[rt].mn >= cntp) return ;
	if(l == r){
		T[rt].mn ++ ;
		T[rt].val = calc(a[l],T[rt].mn);
		return ;
	}
	int mid = l+r >> 1;
	if(L <= mid) modify(rt<<1,l,mid,L,R);
	if(R >  mid) modify(rt<<1|1,mid+1,r,L,R);
	T[rt] = T[rt<<1] + T[rt<<1|1];
}
int main(){
	read(n);read(m);read(p);read(c);
	ph[cntp = 0] = p;
	while(p != 1) ph[++ cntp] = (p = phi(p));
    ph[++ cntp] = 1;
	rep(i,1,n) read(a[i]);
	build(1,1,n);
	int opt,l,r;
	while(m--){
		read(opt);read(l);read(r);
		if(opt == 1) printf("%d
",query(1,1,n,l,r));
		else modify(1,1,n,l,r);
	}
	return 0;
}

T3 : 组合数问题

这道题考场上直接Lucas上的.
这道题应该没有办法从数论的角度上来进行优化.
考虑一下这些组合数的具体含义:
注意 : (r < k))不要问我为什么要强调注意这个.
那么这些组合数的含义可以这么理解:
从共n*k个物品中选出%k为r的数目的物品的方案数.
这样就很明显了.设(f[i][j])表示前i个数中选出%k为j的数目的物品的方案数.
矩乘优化dp转移.(O(k^3log n))

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(ll &x){
	x=0;static char ch;bool flag = false;
	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
ll p;
struct Matrix{
	ll n,m;
	ll s[52][52];
	void clear(ll n,ll m){
		this->n = n;this->m = m;
		memset(s,0,sizeof s);
	}
	friend Matrix operator * (const Matrix &a,const Matrix &b){
		Matrix c;c.clear(a.n,b.m);
		rep(i,0,c.n-1) rep(j,0,c.m-1) rep(k,0,a.m-1){
			c.s[i][j] += 1LL*a.s[i][k]*b.s[k][j] % p;
			if(c.s[i][j] >= p) c.s[i][j] -= p;
		}return c;
	}
};
ll n,k,r;
Matrix A,B;
int main(){
	read(n);read(p);read(k);read(r);
	A.clear(1,k);A.s[0][0] = 1;
	B.clear(k,k);
	rep(i,0,k-1){
		B.s[i][i] += 1;
		B.s[(i-1+k)%k][i] += 1;
	}
	ll x = 1LL*n*k;
	for(;x;x>>=1,B=B*B) if(x&1) A=A*B;
	printf("%lld
",A.s[0][r]);
	return 0;
}

Day2:

T1 :

暂时还不会,挖坑.

T2 : 分手是祝愿

首先考虑对于一个确定的局面如何确定最优策略.
通过很简单的分析可以发现 : 只要从高位到低位确定即可.
然后通过等价变化,将枚举因数变为枚举倍数,可以做到O(nlogn)求出最优解.
通过上面的过程,我们可以得到一个推论 : 最优方案是唯一的.
应该被操作的位是固定的.
所以在进行dp时我们采用如下定义方式 : 设f[i]表示当前的操作中有i个是正确的.
那么通过对下一步的随意选取,很容易得到方程:(f_i = frac{n-i}{n}*f_{i+1} + frac{i}{n}*f_{i-1})
然后这个东西直接利用系数递推进行转移即可.
(f_i = B_i*f_{i-1} + C_i)
那么

[f_i = frac{n-i}{n}*(B_{i+1}*f_i+C_{i+1}) + frac{i}{n}*f_{i-1} + 1 ]

[(1 - frac{n-i}{n}B_{i+1})f_i = frac{i}{n}f_{i-1} + frac{n-i}{n}C_{i+1} + 1 ]

[f_i = frac{frac{i}{n}f_{i-1} + frac{n-i}{n}C_{i+1} + 1}{(1 - frac{n-i}{n}B_{i+1})} ]

可以得到递推关系 :

  • (B_i = frac{frac{i}{n}}{(1 - frac{n-i}{n}B_{i+1})})
  • (C_i = frac{frac{n-i}{n}C_{i+1} + 1}{(1 - frac{n-i}{n}B_{i+1})})
    所以线性计算出系数再线性带入计算即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
    x=0;char ch;bool flag = false;
    while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int mod = 100003;
const int maxn = 100005;
int a[maxn],tag[maxn],b[maxn],c[maxn],f[maxn];
inline int qpow(int x,int p){
    int ret = 1;
    for(;p;p>>=1,x=1LL*x*x%mod) if(p&1) ret=1LL*ret*x%mod;
    return ret;
}
int main(){
    int n,k;read(n);read(k);
    rep(i,1,n) read(a[i]);
    int s = 0;
    per(i,n,1){
        int res = a[i];
        for(int j=i*2;j<=n;j+=i) res^=tag[j];
        tag[i] = res;
        s += res;
    }
    if(s <= k){
        rep(i,1,n) s = 1LL*s*i%mod;
        printf("%d
",s);
    }else{
        b[k] = 0;c[k] = f[k] = k;
        b[n] = c[n] = 1;
        for(int i=n-1,inv;i>k;--i){
            b[i] = i;c[i] = ( 1LL*(n-i)*c[i+1] % mod + n)%mod;
            inv = qpow( (n-1LL*(n-i)*b[i+1]%mod + mod) % mod,mod-2);
            b[i] = 1LL*b[i]*inv % mod;
            c[i] = 1LL*c[i]*inv % mod;
        }
        for(int i=k+1;i<=s;++i){
            f[i] = (1LL*b[i]*f[i-1] + c[i])%mod;
        }
        int ans = f[s];
        rep(i,1,n) ans=ans*1ll*i%mod;
        printf("%d
",ans);
    }
    return 0;
}

T3 : 寿司餐厅

说多了都是泪.
临考前考到了一道模型相似的题.
抱着这题肯定能A的心态去写的.
然后最大权闭合子图没调出来.
不说那么多了... ...
首先这道题是网络流没跑.
重点我们需要解决三个限制 :

  • 选择区间互相的包含关系
  • 不同寿司的第一次取用的额外代价
  • 区间与寿司的对应关系.

我们使用最小割模型.
先考虑第一种限制,我们知道一个区间一定会对这个区间的所有子区间进行限制.
但是肯定不允许我们对所有子区间都进行连边限制
我们发现限制是具有传递性的,所以对于区间[l,r]直接向[l+1,r],[l,r-1]限制即可.

对于第二、三种限制,我们对于每个寿司建一个额外节点,连出流量为(mx^2),向汇点的边.
对于所有有用到过这个寿司的区间向对应的寿司节点连边.
这样完成了限制二、三.

然后处理处理细节就好了.具体的细节是什么.. ..
我懒得写了==
(这份代码在速度榜上目前rank1,如果按运行时间从大到小排序的话)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
    x=0;static char ch;bool flag = false;
    while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 128;
const int maxnode = maxn*maxn;
const int maxedge = maxnode*10;
const int inf = 0x3f3f3f3f;
struct Edge{
    int to,next,cap;
}G[maxedge<<1];
int head[maxnode],cnt = 1;
void add(int u,int v,int c){
    G[++cnt].to = v;
    G[cnt].next = head[u];
    head[u] = cnt;
    G[cnt].cap = c;
}
inline void insert(int u,int v,int c){
    add(u,v,c);add(v,u,0);
}
#define v G[i].to
int q[maxnode],l,r,dis[maxnode];
int S,T;
bool bfs(){
    memset(dis,-1,sizeof dis);
    dis[S] = 0;l = 0;r = -1;
    q[++r] = S;
    while(l <= r){
	int u = q[l++];
        for(int i = head[u];i;i=G[i].next){
            if(dis[v] == -1 && G[i].cap){
                dis[v] = dis[u] + 1;
                q[++r] = v;
            }
        }
    }return dis[T] != -1;
}
int dfs(int u,int f){
    if(u == T || f == 0 ) return f;
    int ret = 0;
    for(int i = head[u];i;i=G[i].next){
        if(dis[v] == dis[u] + 1 && G[i].cap){
            int x = dfs(v,min(G[i].cap,f));
            ret += x;f -= x;
            G[i].cap -= x;
            G[i^1].cap += x;
            if(f == 0) break;
        }
    }return ret;
}
inline int dinic(){
    int ret = 0;
    while(bfs()) ret += dfs(S,inf);
    return ret;
}
#undef v
int nodecnt = 0;
int a[maxn],id[maxn][maxn],idx[maxn];
int main(){
    int n,m;read(n);read(m);
    S = ++ nodecnt;T = ++ nodecnt;
    int mx = 0;
    rep(i,1,n) read(a[i]),mx = max(mx,a[i]);
    rep(i,1,mx){
        idx[i] = ++ nodecnt;
        insert(idx[i],T,m*i*i);
    }
    int x,ans = 0;
    rep(i,1,n) rep(j,i,n) id[i][j] = ++ nodecnt;
    rep(i,1,n){
        rep(j,i,n){
            read(x);
            if(i == j) x -= a[i],insert(id[i][j],idx[a[i]],inf);
            else{
                if(id[i+1][j])insert(id[i][j],id[i+1][j],inf);
                if(id[i][j-1])insert(id[i][j],id[i][j-1],inf);
            }
            if(x > 0) ans += x,insert(S,id[i][j],x);
            else insert(id[i][j],T,-x);
        }
    }
    ans -= dinic();
    printf("%d
",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/Skyminer/p/6758563.html