1. 程式人生 > 其它 >AtCoder Beginner Contest 209 題解

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;
}