test20190816

闲扯

今天考试题好不友好啊。。。

(T1) 怎么是个数学题。。我 (OI) 的数学最弱了。。

(T2) 打个暴力还是有个 (60) 分,还是可以接受蒟蒻的人生目标就止于此了吗

(T3) 为啥又是期望 (DP) 啊,话说这东西(NOIP) 都没怎么考过,有必要隔一两天就考一次吗。。期望感觉和数学一样,完全不懂诶。。

题面

题面

(T1)

Solution

这道题本质上是求一个组合数,即从 (N) 个数中选取 (M) 个不相同的数的方案数。但是这道题有条件限制,即每相邻的两个数中间至少要相隔 (K-1) 个数。

因为我们默认了选取的第一个数是 (1) ,最后一个数是 (N) ,那么除了最后一个 (N) ,每一个数后面都有 (k-1) 个数不能选,一共 ((k-1) imes(m-1)) 个。

再除开最后一个,那么我们剩余可供选择的就有 (N-1-(k-1) imes(M-1)) 个数。

因为我们要将这些数分为 (M-1) 段,每一段至少包含一个数,要求方案数就可以用经典的隔板法。

答案为 (C_{N-2-(k-1) imes(M-1)}^{M-2})

又因为题目要求的是答案的奇偶性,根据卢卡斯定理:

( binom{n}{k} equiv 1 (mod 2)) 当且仅当二进制下 (k) 的各位都不大于 (n) 的对应位,即 (n and k = k) ,其中 (and) 为按位与。

时间复杂度: (O(T))

Code:

#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
	int f=1;char k=getchar();x=0;
	for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
	for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
	x*=f;
}
template<class T>il print(T x){
	if(x/10) print(x/10);
	putchar(x%10+'0');
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
	int res=1,bas=x%mod;
	while(m){
		if(m&1) res=(res*bas)%mod;
		bas=(bas*bas)%mod,m>>=1;
	}
	return res%mod;
}
int T,n,m,k;
int main()
{
	freopen(".in","r",stdin);
	freopen(".out","w",stdout);
	read(T);
    while(T--){
        read(n),read(m),read(k);
        puts(((((n-2-(m-1)*(k-1))&(m-2))==m-2)?"Yes
" : "No
"));
    }
	return 0;
}

(T2)

Solution

这道题的解法巨多,我们机房有4个大佬,每一个的做法都不一样。。。

先考虑 (i,j) 两点间的 (j)(i) 贡献。

(val_{i,j}=dis_{i,j}+c_i-c_j)

由于这是一棵树,有一个套路的做法,即把 (dis_{i,j}) 拆开。

(val_{i,j}=dis_i+dis_j-2 imes dis_{lca}+c_i-c_j)

这里的 (dis) 表示该节点到根结点的距离。

那么当 (i) 确定时,我们再确定 (lca) ,那么我们就只用维护 (Min(dis_j-c_j)) 即可求出点 (i) 的答案。(思路二、三会用到)

思路一:树形DP

对于有根树,一个点所选择的对应点仅在其子树内的情形,
答案可以通过树形 (dp) 递推得到。

考虑选择其子树外的点的情形。观察原式可以发现,如果一
个点的父亲的最优选择不是该点,则其在子树外的最优选择必
然就是其父亲的最优选择。否则,其在子树外的最优选择必然是
其父亲的次优选择。由此,可在 (DFS) 过程中自上而下推出所有点
在其子树外的最优答案。

对每个点子树内外的最优答案取最小值,即可得到每个点的
答案。

复杂度:(O(n))

查询时间复杂度: (O(1))

(ps:) 这个思路感觉好麻烦。。没有线段树暴力换根来得快.

思路二:点分治

因为是处理树上两点间的路径的问题,考虑淀粉质。

按照最开始的思路拆开,我们对当前的分治中心,将它的子树从左向右、从右向左扫两遍(保证了以每一个点为出发点,维护出了到除了根节点和同一子树之外的所有点的贡献),即可得到处于不同子树的两点间的取值。对每一个点维护处它的 (Min) 即是答案。而根结点的贡献则是子树中的的所有点的 (Min)

时间复杂度: (O(N logN))

查询时间复杂度: (O(1))

思路三:线段树换根

还是现将答案拆开,我们考虑确定 (i) 为这棵树的根结点,答案变成了 (Min(dis_j-c[j])) 考虑直接用线段树维护区间最小值。

考虑换根。当我们以 (i) 的一个儿子 (k) 作为新的根的时候,我们只需要将以该节点为根的子树中的节点到根结点的距离减去这条边的长度,其他点到根结点的距离加上这个长度,然后再查询以 (k) 为根结点的子树中的最小值,更新答案即可。

区间加减可以求出 (DFS) 序,然后按照 (DFS) 序来建树。

预处理时间复杂度: (O(N logN))

查询时间复杂度: (O(1))

思路四:我忘了

Code

思路一:

#include <akari>

#define FI "skylines"

IO<1000000, 1000000> io;

cint N = 200003;

struct Node {
    int x, qwq, dep, ans, mi;
    struct Edge *e;
} g[N];
struct Edge {
    Node *v;
    int w;
    Edge *e;
    Edge() {}
    Edge(Node *s, Node *t, cint x) : v(t), w(x), e(s->e) { s->e = this; }
} pool[N * 2], *curr = pool;
void arc(Node *u, Node *v, cint w) {
    new (curr++) Edge(u, v, w);
    new (curr++) Edge(v, u, w);
}
void dfs1(Node *u, Node *f) {
    int &mi = u->mi = u->dep - u->x, tmi = INT_MAX;
    int mc = 0;
    Node *v;
    go (e, u->e) if ((v = e->v) != f) {
        v->dep = u->dep + e->w;
        dfs1(v, u);
        if (v->mi < tmi) tmi = v->mi, mc = 1;
        else if (v->mi == tmi) ++mc;
    }
    if (mi < tmi) mc = 0;
    else if (mi > tmi) mi = tmi;
    else ++mc;
    if (mc == 1 && mi != u->dep - u->x) {
        Node *t;
        int smi = u->dep - u->x;
        go (e, u->e) if ((v = e->v) != f) {
            if (v->mi == mi) t = v;
            else smi = std::min(smi, v->mi), v->qwq = mi;
        }
        t->qwq = smi;
    } else
        go (e, u->e) if (e->v != f) e->v->qwq = mi;
    u->ans = (tmi < INT_MAX) ? u->x - u->dep + tmi : INT_MAX;
}
void dfs2(Node *u, Node *f, cint t) {
    if (t < INT_MAX)
        u->ans = std::min(u->ans, u->dep + u->x + t);
    Node *v;
    go (e, u->e) if ((v = e->v) != f)
        dfs2(v, u, std::min(t, v->qwq - 2 * u->dep));
}
int main() {
#ifndef AKARI
    freopen(FI ".in", "r", stdin);
    freopen(FI ".out", "w", stdout);
#endif

    int n = io;
    rep (i, 1, n)
        io >> g[i].x;
    re (i, 1, n) {
        int u, v;
        io >> u >> v;
        arc(&g[u], &g[v], io);
    }
    dfs1(&g[1], NULL);
    dfs2(&g[1], NULL, INT_MAX);
    int q = io;
    while (q--)
        io << g[(int)io].ans daze;
}

思路二:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read()
{
	int out=0,fh=1;
	char jp=getchar();
	while ((jp>'9'||jp<'0')&&jp!='-')
		jp=getchar();
	if (jp=='-')
		fh=-1,jp=getchar();
	while (jp>='0'&&jp<='9')
		out=out*10+jp-'0',jp=getchar();
	return out*fh;
}
const int inf=2e9+10;
const int MAXN=2e5+10;
int ecnt=0,nx[MAXN<<1],to[MAXN<<1],val[MAXN<<1],head[MAXN];
void addedge(int u,int v,int w)
{
	++ecnt;
	to[ecnt]=v;
	val[ecnt]=w;
	nx[ecnt]=head[u];
	head[u]=ecnt;
}
int n,m,c[MAXN],dist[MAXN],fa[MAXN];
int siz[MAXN],mxson[MAXN],vis[MAXN],Ans[MAXN];
int rt,mi,totsiz;
void Findrt(int u,int fa)
{
	siz[u]=1;
	int mxsiz=0;
	for(int i=head[u];i;i=nx[i])
	{
		int v=to[i];
		if(v==fa || vis[v])
			continue;
		Findrt(v,u);
		siz[u]+=siz[v];
		mxsiz=max(mxsiz,siz[v]);
	}
	mxsiz=max(mxsiz,totsiz-siz[u]);
	if(mxsiz<mi)
		mi=mxsiz,rt=u;
}
int minv,rminv,tmp;
void dfs(int u,int F)
{
	Ans[u]=min(Ans[u],minv+dist[u]+c[u]);
	tmp=min(tmp,dist[u]-c[u]);
	rminv=min(rminv,dist[u]-c[u]);
	for(int i=head[u];i;i=nx[i])
	{
		int v=to[i];
		if(v!=F && !vis[v])
		{
			dist[v]=dist[u]+val[i];
			dfs(v,u);
		}	
	}
}
int stk[MAXN],tp=0;
void solve(int u)
{
	dist[u]=0;
	minv=-c[u],rminv=inf;
	tp=0;
	for(int i=head[u];i;i=nx[i])
	{
		int v=to[i];
		if(vis[v])
			continue;
		stk[++tp]=i;
		tmp=inf;
		dist[v]=val[i];
		dfs(v,u);
		minv=min(minv,tmp);
	}
	minv=dist[u]-c[u];
	for(int i=tp;i>=1;--i)
	{
		int v=to[stk[i]];
		tmp=inf;
		dist[v]=val[stk[i]];
		dfs(v,u);
		minv=min(minv,tmp);
	}
	Ans[u]=min(Ans[u],rminv+c[u]);
}
void Divide(int u)
{
	vis[u]=1;
	solve(u);
	for(int i=head[u];i;i=nx[i])
	{
		int v=to[i];
		if(vis[v])
			continue;
		totsiz=siz[v];
		mi=inf;
		Findrt(v,0);
		Divide(rt);
	}
}
void init()
{
	mi=inf,totsiz=n;
	Findrt(1,0);
	Divide(rt);
}
int main()
{
	freopen("skylines.in","r",stdin);
	freopen("skylines.out","w",stdout);
	n=read();
	for(int i=1;i<=n;++i)
		c[i]=read(),Ans[i]=inf;
	for(int i=1;i<n;++i)
	{
		int u=read(),v=read(),w=read();
		addedge(u,v,w);
		addedge(v,u,w);
	}
	init();
	int m=read();
	for(int i=1;i<=m;++i)
	{
		int x=read();
		printf("%d
",Ans[x]);
	}
	return 0;
}

思路三:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 200010
#define INF 0x7ffffff
template <typename T>inline T read()
{
    register T sum=0;
    register char cc=getchar();
    int sym=1;
    while(cc!='-'&&(cc>'9'||cc<'0'))cc=getchar();
    if(cc=='-')sym=-1,cc=getchar();
    sum=sum*10+cc-'0';
    cc=getchar();
    while(cc>='0'&&cc<='9')sum=sum*10+cc-'0',cc=getchar();
    return sym*sum;
}
template <typename T>inline T read(T &a)
{
    a=read<T>();
    return a;
}
template <typename T,typename... Others> inline void read(T& a, Others&... b)
{
    a=read(a);
	read(b...);
}
struct Edge
{
	int v;
	int w;
	Edge *next;
	Edge(int a=0,int b=0,Edge *c=NULL)
	{
		v=a;
		w=b;
		next=c;
	}
}*head[maxn];
struct Node
{
	int v;
	int tag;
	Node()
	{
		v=INF;
		tag=0;
	}
}node[maxn<<2];
int n,m,cnt,v[maxn],fa[maxn],ans[maxn],dis[maxn],dfn[maxn],tfn[maxn],siz[maxn];
void down(int k)
{
	if(node[k].tag)
	{
		node[k<<1].v-=node[k].tag;
		node[k<<1].tag+=node[k].tag;
		node[k<<1|1].v-=node[k].tag;
		node[k<<1|1].tag+=node[k].tag;
		node[k].tag=0;
	}
}
void build(int k,int l,int r)
{
	if(l==r)
	{
		node[k].v=dis[tfn[l]]-v[tfn[l]];
		return ;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	node[k].v=min(node[k<<1].v,node[k<<1|1].v);
}
void change(int k,int l,int r,int x,int y,int z)
{
	if(l>=x&&r<=y)
	{
		node[k].v-=z;
		node[k].tag+=z;
		return ;
	}
	down(k);
	int mid=(l+r)>>1;
	if(x<=mid)change(k<<1,l,mid,x,y,z);
	if(y>mid)change(k<<1|1,mid+1,r,x,y,z);
	node[k].v=min(node[k<<1].v,node[k<<1|1].v);
}
int query(int k,int l,int r,int x,int y)
{
	if(l>=x&&r<=y)return node[k].v;
	down(k);
	int mid=(l+r)>>1,ans=INF;
	if(x<=mid)ans=min(ans,query(k<<1,l,mid,x,y));
	if(y>mid)ans=min(ans,query(k<<1|1,mid+1,r,x,y));
	return ans;
}
void dfs1(int k)
{
	dfn[k]=++cnt;
	tfn[cnt]=k;
	siz[k]=1;
	for(Edge *i=head[k];i!=NULL;i=i->next)
	{
		if(i->v==fa[k])continue;
		fa[i->v]=k;
		dis[i->v]=dis[k]+i->w;
		dfs1(i->v);
		siz[k]+=siz[i->v];
	}
}
void print(int k,int l,int r)
{
	if(l==r)
	{
		printf("%d ",node[k].v);
		return ;
	}
	down(k);
	int mid=(l+r)>>1;
	print(k<<1,l,mid);
	print(k<<1|1,mid+1,r);
}
void dfs2(int k)
{
	for(Edge *i=head[k];i!=NULL;i=i->next)
	{
		if(i->v==fa[k])continue;
		if(1<=dfn[i->v]-1)
			change(1,1,n,1,dfn[i->v]-1,-i->w);
		if(dfn[i->v]+siz[i->v]<=n)
			change(1,1,n,dfn[i->v]+siz[i->v],n,-i->w);
		change(1,1,n,dfn[i->v],dfn[i->v]+siz[i->v]-1,i->w);
		ans[i->v]=INF;
		if(1<=dfn[i->v]-1)
			ans[i->v]=min(ans[i->v],query(1,1,n,1,dfn[i->v]-1));
		if(dfn[i->v]+1<=n)
			ans[i->v]=min(ans[i->v],query(1,1,n,dfn[i->v]+1,n));
		ans[i->v]+=v[i->v];
		dfs2(i->v);
		if(1<=dfn[i->v]-1)
			change(1,1,n,1,dfn[i->v]-1,i->w);
		if(dfn[i->v]+siz[i->v]<=n)
			change(1,1,n,dfn[i->v]+siz[i->v],n,i->w);
		change(1,1,n,dfn[i->v],dfn[i->v]+siz[i->v]-1,-i->w);
	}
}
int main()
{
	freopen("skylines.in","r",stdin);
	freopen("skylines.out","w",stdout);
	n=read<int>();
	for(int i=1;i<=n;i++)v[i]=read<int>();
	for(int i=1;i<n;i++)
	{
		int x=read<int>(),y=read<int>(),z=read<int>();
		head[x]=new Edge(y,z,head[x]);
		head[y]=new Edge(x,z,head[y]);
	}
	dfs1(1);
	build(1,1,n);
	if(n>=2)ans[1]=query(1,1,n,2,n)+v[1];
	dfs2(1);
	m=read<int>();
	for(int i=1;i<=m;i++)
		printf("%d
",ans[read<int>()]);
    return 0;
}

(T3)

Solution

一个数列的权值和与数列中数的顺序无关,只与每种数的个
数有关

考虑 (DP),状态中需要记录每种数的个数

可以发现,将得到的数列排序后,相邻两数的差只能为 (0)
(1)

用二进制序列维护原数列的差分数组,即可表示数列中每种
数的个数

(f(i,S)) 表示生成了 (i) 个数,已有数列的差分为 (S) 的方案数

则每种数列出现的概率 (P_S=frac{f(m,S)}{m!})

转移时枚举前一个数的大小,修改状态即可

事先预处理出 (tot_S) 表示状态 (S) 所表示的数列的权值和,最后的答案即为 (sumlimits_{S}P_Stot_S)

时间复杂度: (O(mcdot2^m)) 。注意做除法时用乘法逆元计算。

以上均为题解内容,蒟蒻表示看得一脸懵逼

Code

#include <akari>

#define FI "kiseki"

IO<100, 100> io;
const int N = 30, S = (1 << 24) + 5, mo = 998244353;

void add(int &x, cint v) {
    if ((x += v) >= mo)
        x -= mo;
}
int f_[S], g_[S], a[N], c[N], p[N], inv[N];

int main() {
#ifndef AKARI
    freopen(FI ".in", "r", stdin);
    freopen(FI ".out", "w", stdout);
#endif

    int n;
    io >> n >> n;
    rep (i, 1, n)
        io >> a[i];
    
    inv[1] = 1;
    rep (i, 2, n)
        inv[i] = (ll)(mo - mo / i) * inv[mo % i] % mo;

    int *f = f_, *g = g_;
    f[0] = 1;
    c[0] = 1;
    rep (i, 2, n) { 
        re (s, 0, 1 << (i - 2)) { 
            int cur = 1;
            c[1] = 1;
            re (j, 0, i - 2) {
                if ((s >> j) & 1) {
                    c[++cur] = 1;
                    p[cur] = j;
                } else
                    ++c[cur];
            }
            rep (j, 0, cur) {
                int t;
                if (j == 0)
                    t = s << 1;
                else if (j == cur)
                    t = s | (1 << (i - 2));
                else {
                    int h = s & ((2 << p[j + 1]) - 1); 
                    t = ((s ^ h) << 1) | h;
                }
                g[t] = (g[t] + (ll)c[j] * inv[i] % mo * f[s]) % mo;
            }
            f[s] = 0;
        }
        std::swap(f, g);
    }

    int ans = 0;
    re (s, 0, 1 << (n - 1)) {
        int cur = 1;
        int sum = a[1];
        re (j, 0, n - 1) {
            if ((s >> j) & 1)
                ++cur;
            sum += a[cur];
        }
        ans = (ans + (ll)sum * f[s]) % mo;
    }
    io << ans daze;

    return 0;
}

总结

蒟蒻还是太弱了,加油学吧。。

原文地址:https://www.cnblogs.com/TheShadow/p/11367336.html