AtCoder Beginner Contest 209 题解

本场链接:AtCoder Beginner Contest 209

C - Not Equal

不难注意到:\(A_i\)的次序无关,因为每个元素都不同,只需要考虑每个元素在他的区间内的取值即可.因此按上升对\(C_i\)排序,由于整个数组成上升,所以当做到\(C_i\)的时候,上限相当于去掉了\(i - 1\)个元素,如此即可统计答案.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)

const int N = 2e5+7,MOD = 1e9 + 7;
int c[N];

int main()
{
    int n;scanf("%d",&n);
    forn(i,1,n) scanf("%d",&c[i]);
    sort(c + 1,c + n + 1);

    int res = 1;
    forn(i,1,n) res = 1ll * res * max(0,c[i] - i + 1) % MOD;
    printf("%d\n",res);
    return 0;
}

D - Collision

弱智题,两个点会走到一个点是距离为偶数,反之是奇数.求树上任意两点距离即可.

#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5+7,M = 4e5+7,LIM = 17;
int edge[M],succ[M],cost[M],ver[N],idx;
int n,m,hh,tt,q[M];
int depth[N],fa[N][LIM + 3],dist[N];
void add(int u,int v,int w)
{
	edge[idx] = v;
	cost[idx] = w;
	succ[idx] = ver[u];
	ver[u] = idx++;
}
void bfs()
{
	memset(depth,0x3f,sizeof depth);
	memset(dist,0x3f,sizeof dist);
	depth[1] = 0;depth[0] = -1;q[++tt] = 1;dist[1] = 0;
	while(hh <= tt)
	{
		int u = q[hh++];
		for(int i = ver[u];~i;i = succ[i])
		{
			int v = edge[i];
			if(depth[v] > depth[u] + 1)
			{
				dist[v] = dist[u] + cost[i];
				depth[v] = depth[u] + 1;
				fa[v][0] = u;
				q[++tt] = v;
				for(int k = 1;k <= LIM;++k)
					fa[v][k] = fa[fa[v][k - 1]][k - 1];
			}
		}
	}
}
int lca(int x,int y)
{
	if(depth[x] < depth[y])	swap(x,y);
	for(int i = LIM;i >= 0;--i)
		if(depth[fa[x][i]] >= depth[y])
			x = fa[x][i];
	if(x == y)	return x;
	for(int i = LIM;i >= 0;--i)
		if(fa[x][i] != fa[y][i])
			x = fa[x][i],y = fa[y][i];
	return fa[x][0];
}
int main()
{
	memset(ver,-1,sizeof ver);
	scanf("%d%d",&n,&m);
	for(int i = 1;i < n;++i)
	{
		int u,v;scanf("%d%d",&u,&v);
		add(u,v,1);add(v,u,1);
	}
	bfs();
	while(m--)
	{
		int u,v;scanf("%d%d",&u,&v);
		if((dist[u] + dist[v] - 2 * dist[lca(u,v)]) % 2 == 0)   puts("Town");
		else puts("Road");
	}
    return 0;
}

E - Shiritori

首先优化建图:如果直接建图整个图的边数会达到\(N^2\)无法承受.将所有首尾三个元素相同的元素合并到一个点,边由每个\(s_i\)决定:首对应的点是\(u\)尾对应的点是\(v\)则建\(u->v\)的边.

考虑求答案:如果这张图上没有环显然是可以直接递推的:在反图上跑拓扑排序即可.但是有环其实也不影响,最终所有没有确定状态的点都是平局.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
#define x first
#define y second

const int N = 52 * 52 * 52 + 7,M = 2 * N,C = 2e5+7;
char s[10];
vector<pii> edges(C);
vector<int> E[N];
int deg[N],ans[N];

inline int gpos(char a)
{
    if(a >= 'A' && a <= 'Z')    return a - 'A';
    return a - 'a' + 26;
}
inline int gid(char a,char b,char c)
{
    return gpos(a) * 52 * 52 + gpos(b) * 52 + gpos(c);
}

int main()
{
    int n;scanf("%d",&n);
    forn(i,1,n)
    {
        scanf("%s",s + 1);int len = strlen(s + 1);
        edges[i] = {gid(s[1],s[2],s[3]),gid(s[len - 2],s[len - 1],s[len])};
        ++deg[edges[i].x];
        E[edges[i].y].push_back(edges[i].x);
    }

    queue<int> q;memset(ans,-1,sizeof ans);
    forn(i,0,N - 1) if(!deg[i]) q.push(i),ans[i] = 0;
    while(!q.empty())
    {
        int u = q.front();q.pop();
        for(auto& v : E[u])
        {
            if(ans[v] != -1)    continue;
            --deg[v];
            if(ans[u] == 0) ans[v] = 1,q.push(v);
            else if(!deg[v])    ans[v] = 0,q.push(v);
        }
    }
    forn(i,1,n)
    {
        if(ans[edges[i].y] == -1)   puts("Draw");
        else if(ans[edges[i].y] == 0)   puts("Takahashi");
        else puts("Aoki");
    }
    return 0;
}

F - Deforestation

这个题如果只是要求最小值,是一个出烂的玩意,但是要求方案数,手玩一下发现原来求最小值的贪心对于求方案数没有任何贡献.

考虑从题目本身做:对于每个\(H_i\)对于答案的贡献是多少?当删去\(H_i\)的时候一定会产生\(H_i\)的贡献,当删去\(H_{i+1}\)的时候,\(H_i\)会产生贡献当且仅当\(H_{i+1}\)是先于\(H_i\)被砍的.同理当删去\(H_{i-1}\)的时候,\(H_i\)会产生贡献当且仅当\(H_{i-1}\)先于\(H_i\)被删掉.

从前往后考虑每个元素\(i \in [1,n-1]\):

  • 若\(H_i < H_{i+1}\),那么\(H_i\)的删去时间应该晚于\(H_{i+1}\),否则会让\(H_{i+1}\)往答案贡献两次而不是\(H_i\)贡献两次(显然后者更优).
  • 若\(H_i > H_{i+1}\),那么\(H_i\)的删去时间应该早于\(H_{i+1}\),原因同上.
  • 若两者相同,显然先后不会再对贡献有影响.

如此可以考虑一个dp:

  • 状态:\(f[i][j]\)表示分配前\(i\)个元素,末尾元素被安排在\(j\)位置上的方案数.
  • 入口:\(f[1][1] = 1\)其他为\(0\).
  • 转移:对于\(i \in[2,n]\),若\(H_i == H_{i-1}\)则\(f[i][j] = \sum\limits_{k = 1}^i f[i - 1][k]\),若\(H_i < H_{i-1}\),则\(f[i][j] = \sum\limits_{k = j}^i f[i - 1][k]\),若\(H_i > H_{i-1}\),则\(f[i][j] = \sum\limits_{k = 1}^{j - 1} f[i - 1][k]\).当\(j == k\)时,认为是\(H_i\)插入到\(H_{i-1}\)的前面.
  • 出口:\(ans = \sum\limits_{k = 1}^n f[n][i]\).

转移部分,在每次处理完\(i\)维的转移后,记录本维状态的前缀和,即可将转移复杂度降到可以承受的\(O(n^2)\).

注意给\(sum[1] = 1\)初值.

($j == k的情况如何处理有些怪,以后补)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)

const int N = 4005,MOD = 1e9+7;
int H[N],f[N][N],sum[N];

int main()
{
    int n;scanf("%d",&n);
    forn(i,1,n) scanf("%d",&H[i]);

    f[1][1] = sum[1] = 1;
    forn(i,2,n)
    {
        forn(j,1,i)
        {
            if(H[i] == H[i - 1])    f[i][j] = sum[i - 1];
            else if(H[i] < H[i - 1])    f[i][j] = ((sum[i - 1] - sum[j - 1]) % MOD + MOD) % MOD;
            else f[i][j] = sum[j - 1];
        }
        forn(j,1,n) sum[j] = (f[i][j] + sum[j - 1]) % MOD;
    }
    
    int res = 0;
    forn(i,1,n) res = (res + f[n][i]) % MOD;
    printf("%d\n",res);
    return 0;
}

上一篇:「游记」AtCoder Beginner Contest 209


下一篇:【leetcode-Python】-滑动窗口-209. Minimum Size Subarray Sum