2019.10.29 nowcoder contest 1

\(A-\)仓鼠的石子游戏

题意:

https://ac.nowcoder.com/acm/problem/53975

仓鼠和兔子被禁止玩电脑,无聊的他们跑到一块空地上,空地上有许多小石子。兔子捡了很多石子,然后将石子摆成n个圈,每个圈由a[i]个石子组成。然后兔子有两根彩色笔,一支红色一支蓝色。兔子和仓鼠轮流选择一个没有上色的石子涂上颜色,兔子每次可以选择一个还未染色的石子将其染成红色,而仓鼠每次可以选择一个还未染色的石子将其染成蓝色,并且仓鼠和兔子约定,轮流染色的过程中不能出现相邻石子同色,谁不能操作他就输了。假设他们两个都使用了最优策略来玩这个游戏,并且兔子先手,最终谁会赢得游戏?

数据范围

对于前\(100\%\)的数据,满足\(1\leqslant n \leqslant 10^3,1\leqslant a[i]\leqslant 10^9,1\leqslant T\leqslant 10^2\)

分析:

我们首先考虑只有一堆石子的情况,发现只有当石子的数量为\(1\)时,先手必胜。

简单地证明一下。

因为在最终的局面中我们填了的石子它们的颜色依然是交替出现的,因为如果两个颜色相同的石子相邻,那么它们的中间的那个石子是可以填上与它们不同的颜色的,所以最后我们不能操作的时候,填上了的颜色数一定是偶数,也就是说后手进行了最后的一次操作,那么先手必败,只有当\(a=1\)的时候,先手才会获胜。

而对于多堆石子的情况,我们观察有几堆石子是只有一个石子的,如果这样的堆数为偶数,那么兔子相当于依然是先手,若为奇数的话,二者所面对的局面就会调换。

\(Code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
    
const int maxn=1e3+10;
    
int T,n;
    
int a[maxn];
    
template<class T>void read(T &x)
{
    bool f=0;char ch=getchar();x=0;
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    if(f) x=-x;
}
    
int main()
{
    read(T);
    while(T--)
    {
    	int cnt=0;
    	read(n);
    	for(int i=1;i<=n;++i)
    	{
    		read(a[i]);
    		if(a[i]==1) ++cnt;
    	}
    	if(cnt&1)
    	{
    		printf("rabbit\n");
    	}
    	else printf("hamster\n");
    }
    return 0;
}

\(B-\)乃爱与城市拥挤程度

题意:

https://ac.nowcoder.com/acm/problem/53976

乃爱天下第一可爱!

乃爱居住的国家有\(n\)座城市,这些城市与城市之间有\(n-1\)条公路相连接,并且保证这些城市两两之间直接或者间接相连。

我们定义两座城市之间的距离为这两座城市之间唯一简单路径上公路的总条数。

当乃爱位于第x座城市时,距离城市x距离不大于k的城市中的人都会认为乃爱天下第一可爱!

认为乃爱天下第一可爱的人们决定到乃爱所在的城市去拜访可爱的乃爱。我们定义这些城市的拥挤程度为:

距离城市\(x\)距离不大于\(k\)的城市中的人到达城市\(x\)时经过该城市的次数。

例如:

img

假设\(k=2\),乃爱所在的城市是\(1\)号城市,树结构如上图所示时,受到影响的城市为\(1,2,3,4,5\),因为五个城市距离\(1\)号城市的距离分别为:\(0,1,2,2,2\),所以这五个城市都会认为乃爱天下第一。

\(1\)号城市到\(1\)号城市经过了\(1\)号城市。

\(2\)号城市到\(1\)号城市经过了\(1\)号、\(2\)号城市。

\(3\)号城市到\(1\)号城市经过了\(1\)号、\(2\)号、\(3\)号城市。

\(4\)号城市到\(1\)号城市经过了\(1\)号、\(2\)号、\(4\)号城市。

\(5\)号城市到\(1\)号城市经过了1号、\(2\)号、\(5\)号城市。

所以\(1\)号城市的拥挤程度是\(5,2\)号城市的拥挤程度是\(4,3\)号、\(4\)号、\(5\)号城市的拥挤程度都是\(1\)

现在小\(w\)想要问你当乃爱依次位于第\(1、2、3、4、5...n\)座城市时,有多少座城市中的人会认为乃爱天下第一,以及受到影响城市的拥挤程度的乘积,由于这个数字会很大,所以要求你输出认为乃爱天下第一的城市拥挤程度乘积\(mod 10^9+7\)后的结果。

数据范围:

对于前\(100\%\)的测试点满足\(1\leqslant n\leqslant 10^5,1\leqslant k\leqslant 10\),树结构为手动构造。

分析:

树形\(DP\),而这种是属于无根树\(DP\),一般而言,有两种做法,一是换根法,一是\(up\) \(and\) \(down\)

换根法

我们先随便找一个点作为根,然后一遍树形\(DP\)下去先存储每个节点的\(DP\)信息。

然后我们考虑相邻的节点之间进行换根,我们发现这个时候其实影响到的信息是比较少的,一般只会影响到\(x\)以及这个与它进行交换的儿子节点\(y\)

如果我们的\(DP\)方程中是类似加加减减或者乘乘除除这样的操作,这样是非常好维护的,因为他们有着对应的逆操作,而如果是像取\(max\)或者是取\(min\)这样的操作,我们一般是不能直接维护的,这个时候就需要一些数据结构来维护,所以这个时候就推荐使用下面的这种方法了。

\(up\) \(and\) \(down\)

我们选定一个节点作为根后,先自下而上做一遍\(up\)\(DP\),再做一个自上而下的子树的\(down\)\(DP\)

然后我们再每个节点出合并两个\(DP\)信息,就可以得到以每个点为根的\(DP\)信息了。

\(Code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
#define int long long
using namespace std;
    
const int maxn=1e5+10;
const int mod=1e9+7;
    
int sum[maxn][15];//计算到i的距离不大于j的所有城市的拥挤程度之积
int sz[maxn][15];//计算到i的距离不大于j的点有多少个
    
int n,K;
    
ll fpow(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1) ans=(ans*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return ans;
}
    
int head[maxn],tot;
struct Edge
{
    int to,nxt;
    Edge(){};
    Edge(int to,int nxt):to(to),nxt(nxt){};
}ed[maxn<<1];
void add(int u,int v)
{
    ed[++tot]=Edge(v,head[u]);
    head[u]=tot;
    ed[++tot]=Edge(u,head[v]);
    head[v]=tot;
}
    
template<class T>void read(T &x)
{
    bool f=0;char ch=getchar();x=0;
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    if(f) x=-x;
}
    
void dfs(int u,int fa)
{
    for(int i=0;i<=K;++i) sz[u][i]=sum[u][i]=1;
    for(int i=head[u];i;i=ed[i].nxt)
    {
        int v=ed[i].to;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=1;j<=K;++j)
        {
            sz[u][j]+=sz[v][j-1];
            sum[u][j]=sum[u][j]*sum[v][j-1]%mod*sz[v][j-1]%mod;
        }
    }
}
    
void rdfs(int u,int fa)
{
    for(int i=head[u];i;i=ed[i].nxt)
    {
        int v=ed[i].to;
        if(v==fa) continue;
        for(int j=K;j>=2;--j)
        {
            int SZ=sz[u][j-1]-sz[v][j-2];
            sum[v][j]=sum[v][j]*sum[u][j-1]%mod*SZ;
            sum[v][j]=sum[v][j]*fpow(sum[v][j-2]*sz[v][j-2]%mod,mod-2)%mod;
            sz[v][j]+=SZ;
        }
        ++sz[v][1];//因为上面的循环中并没有枚举到length=1的情况
        rdfs(v,u);
    }
}
    
signed main()
{
    read(n);read(K);
    int u,v;
    for(int i=1;i<=n-1;++i) read(u),read(v),add(u,v);
    dfs(1,0);
    rdfs(1,0);
    for(int i=1;i<=n;++i) printf("%d ",sz[i][K]);
    printf("\n");
    for(int i=1;i<=n;++i) printf("%lld ",sum[i][K]*sz[i][K]%mod);
    printf("\n");
    return 0;
}

\(C-\)\(w\)的魔术扑克

题意:

https://ac.nowcoder.com/acm/problem/53980

\(k\)张正反面的卡片,打出时只能选择一面,查询\(q\)次,每次问是否能组成\(l\)\(r\)的顺子。

数据范围:

对于前\(100\%\)的测试点,保证\(1\leqslant n\leqslant 10^5,1\leqslant k\leqslant 10^5,1\leqslant q\leqslant 10^5, 1\leqslant l\leqslant r\leqslant n\)

分析:

构造图论模型。

因为我们一张牌只能选一面,对于这种限制条件,我们很自然地想到了二分图。

我们把一张牌所对应的两个面值连边,我们发现如果连出了一个环的话,这个环中的所有面值都是可以被保证出一次的。

所以这个时候我们可以想到如果我们的顺子中出现了树,那么这个顺子就是不能被得到的。

判断一个连通块是环还是树,我们可以使用并查集得到。

对于每一棵树,我们找到这棵树的节点的最小值和最大值记为\(l_i,r_i\)

接下来的问题就转化为了一个线段覆盖的经典问题:

我们给定l,r,问是否有一个\([l_i,r_i]\)被这个\([l,r]\)完整地覆盖,如果有,那么说明这个顺子是不能被打出的,我们输出\(No\),否则的话就输出\(Yes\)

对于这样的一个问题,题解给出了一个经典\((?)\)做法:
我们把\(r_i\)排序然后离线树状数组,看是否包含\(l_i\)即可,我们就在左端点的位置上插入\(1\),最后对\([l,r]\)求和,看区间的和是否为\(0\)即可。

\(Code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
using namespace std;

const int maxn=1e5+10;

int n,k,Q;

int fa[maxn];
int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	fa[fx]=fy;
}

template<class T>void read(T &x)
{
	bool f=0;char ch=getchar();x=0;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	if(f) x=-x;
}

vector<int> p[maxn];

int maxx,minn;

struct Node
{
	int l,r,id;
	bool operator < (const Node &rhs) const
	{
		return r<rhs.r;
	}
}ask[maxn];

vector<int> g;

vector<Node> f;

int vis[maxn];

int ans[maxn];

void dfs(int u)
{
	if(vis[u]) return;
	vis[u]=1;
	maxx=max(maxx,u),minn=min(minn,u);
	for(int i=0;i<p[u].size();++i) dfs(p[u][i]);
}

int main()
{
	read(n);read(k);
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=k;++i)
	{
		int x,y;
		read(x);read(y);
		if(find(x)==find(y)) g.push_back(x);//x和y本来已经在同一个并查集了,再连上它们的话就
        //可以构成一个环
		else p[x].push_back(y),p[y].push_back(x),merge(x,y);
	}
	for(int i=0;i<g.size();++i) dfs(g[i]);//把可以完全提供的给先dfs一遍,因为可能一些树上的
    //点与环上的点有重合
	for(int i=1;i<=n;++i)
	{
		if(!vis[i])
		{
			maxx=minn=i;
			dfs(i);
			f.push_back(Node{minn,maxx,0});
		}
	}
	read(Q);
	for(int i=1;i<=Q;++i) read(ask[i].l),read(ask[i].r),ask[i].id=i;
	sort(ask+1,ask+Q+1);
	sort(f.begin(),f.end());
	int maxl=0;//可以不用使用树状数组即可实现
	for(int i=1,j=0;i<=Q;++i)
	{
		while(j<f.size()&&f[j].r<=ask[i].r) maxl=max(maxl,f[j].l),++j;
		if(maxl>=ask[i].l) ans[ask[i].id]=0;
		else ans[ask[i].id]=1;
	}
	for(int i=1;i<=Q;++i)
	{
		if(ans[i]) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

posted on 2019-11-07 21:24  dolires  阅读(186)  评论(0)    收藏  举报

导航