CSP-S 2019 第二輪 DAY1 簡單解析(含代碼)

第一題:格雷碼,構造,逆向思維

第二題:括號樹,括號序列,棧,dfs,構造

第三題:樹上的數,貪心,鏈表,構造

 

D1T1:格雷碼

思路:第一眼看到格雷碼感覺格外親切,年紀大了,熬夜熬不動了,記憶力也衰退了不少,隱約記得格雷碼曾經在某一次初賽的試題裏出現過,當然也可能是模擬賽。包括SCOI 2015年還有一題《超級格雷碼》,騰訊2016年筆試有一題是生成格雷碼,LeetCode (一個面試海外IT公司很有用的題庫)也有格雷碼的題目,格雷碼和二進制之間的轉換應該是一個經典問題。

迴歸到本題當中,本題充分考察了選手逆向思維,如果80%的數據,採用構造格雷碼的方法,那麼根據k反推格雷碼的規律也應該能夠想出來。如果K的當前位爲1,則格雷碼該位也爲1,然後把K剩下未處理的位取反,如果K的當前位爲0,則對應的格雷碼也爲0,但不取反, 可以結合代碼理解,建議動動筆推導一下。

#include<iostream>
#include<cstdio>

using namespace std;

int main() {
    freopen("code.in","r",stdin);
    freopen("code.out","w",stdout);
    unsigned long long n, k;
    cin >> n >> k;

    for(long long i = n-1; i >= 0; --i) {
        if ((k >> i)&1) {
            cout << "1";
            k = ~k;
        } else {
            cout << "0";
        }
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

 

D1T2: 括號樹

思路:括號序列和樹上問題結合,點贊。其次特殊性質fi = i - 1 是一條鏈了,都是很好的得部分分的性質,鏈上合法括號序列很簡單,所以這題沒有50分以上的選手都值得反思。

括號序列如果你能想到stack然後計算對答案的貢獻,其實還是可做的,首先建樹從樹根往下跑dfs,用棧來記錄途中的括號,再考慮到一個位置對答案有貢獻,能貢獻的肯定是右括號,過程中記錄待匹配的左括號即可,注意疊加即可,如果你想清楚鏈的問題,相信樹的問題也能想通。所以本題仍舊是一個樹的經典問題,如何在dfs過程中維護我們想要的數據。

注意結果開long long,在oitiku上試了一下不開longlong,只有50分。。。如果真的是longlong卡50分,還不如在題目裏提示一下大家結果超int,RP++

#include <iostream>
#include <cstdio>
#include <stack>

using namespace std;
const int maxn = 500000 + 10;

struct Edge {
	int v, next;
} e[2 * maxn];

int n, fa[maxn], f[maxn], cnt[maxn], tot = 0; 
string s;
stack<int> stk;
long long ans = 0, current = 0;

void add(int u, int v) {
	tot += 1;
	e[tot].v = v;
	e[tot].next = f[u];
	f[u] = tot;
}

void dfs(int x) {
	int cur = 0;
	if (!stk.empty() && s[stk.top() - 1] == '(' && s[x - 1] == ')') {
		cur = stk.top();
		stk.pop();
		cnt[x] = cnt[fa[cur]] + 1;
	} else {
		cnt[x] = 0;
		stk.push(x);
	}

	current += cnt[x];
	ans = ans ^ (x * current);

	for (int i = f[x]; i > 0; i = e[i].next) {
		int v = e[i].v;
		dfs(v);
	}
	current -= cnt[x];

	if (cur)
		stk.push(cur);
	else
		stk.pop();

 }
int main() {
	freopen("brackets.in","r",stdin);
    freopen("brackets.out","w",stdout);
	cin >> n;
	cin >> s;
	
	for (int i = 2; i <= n; ++i) {
		scanf("%d", &fa[i]);
		add(fa[i], i);
	} 
	dfs(1);
	cout << ans << endl;
	fclose(stdin);
	fclose(stdout);
	return 0;
}



 

D1T3:樹上的數

題目大意是帶權樹,n-1條邊,刪邊會調換連接這條邊的兩個點的權值,最後按點權排序,求結點編號的字典序最小的解。本題難度是有點大,但部分分給的也不少。

思路:考試時長3.5小時的情況下,第三題有鏈,有菊花圖(n-1度的點是大菊花)拿個60分,豈不是DAY1 260還是美滋滋的。不少能力出色的選手由於無法駕馭自己的能力,導致最後還不如專心寫部分分,這樣的例子每年都數不勝數。鏈和菊花圖本題給分數不低,且作爲突破口思考是非常值得的。對於普通選數從構思到寫和調試,一共3.5小時還是有點緊。

根據字典序最小,前面儘可能的小,大概能摸到一些貪心的思路。

本題出人已經在知乎上給出做法了,寫的很詳細:

https://www.zhihu.com/question/351347604/answer/896344216

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;
struct Edge
{
     int u, v, next;
} e[2005 * 2];

int n, p[2005];

int pre[2005][2005], nxt[2005][2005];
int cnt[2005], f[2005], ans[2005], tot = 0;

void add(int u, int v)
{
     tot += 1;
     e[tot].u = u;
     e[tot].v = v;
     e[tot].next = f[u];
     f[u] = tot;
}

bool check(int l, int m, int r)
{
     if (pre[m][r] == -1)
          return false;
     if (nxt[m][l] == -1)
          return false;
     if (nxt[m][l] == r)
          return false;
     if (nxt[m][l] == 0 && pre[m][r] == n + 1 && cnt[m] != 2)
          return false;

     return true;
}

void un(int l, int m, int r)
{
     nxt[m][pre[m][r]] = nxt[m][l];
     pre[m][nxt[m][l]] = pre[m][r];
     pre[m][r] = nxt[m][l] = -1;
     cnt[m]--;
}

bool remove(int x, int y, int fa)
{
     if (x == y)
     {
          un(fa, x, n + 1);
          return true;
     }
     for (int i = f[x]; i; i = e[i].next)
     {
          int v = e[i].v;
          if (v == fa)
               continue;
          if (remove(v, y, x))
          {
               un(fa, x, v);
               return true;
          }
     }
     return false;
}

int find(int x, int fa)
{
     int result = n;
     if (check(fa, x, n + 1) && x < n)
          result = x;

     for (int i = f[x]; i; i = e[i].next)
     {
          int v = e[i].v;
          if (v == fa)
               continue;
          if (check(fa, x, v))
               result = min(result, find(v, x));
     }
     return result;
}

void work()
{
     cin >> n;
     tot = 0;
     for (int i = 0; i <= n; ++i)
          f[i] = 0;

     for (int i = 1; i <= n; ++i)
          scanf("%d", &p[i]);
     for (int i = 1; i <= n; ++i)
     {
          for (int j = 0; j <= n + 2; ++j)
               pre[i][j] = nxt[i][j] = j;
     }

     for (int i = 1; i <= n; ++i)
          cnt[i] = 2;
     for (int i = 1; i < n; ++i)
     {
          int u, v;
          scanf("%d%d", &u, &v);
          add(u, v);
          add(v, u);
          cnt[u] += 1;
          cnt[v] += 1;
     }

     for (int i = 1; i <= n; ++i)
     {
          int np = find(p[i], 0);
          ans[i] = np;
          remove(p[i], np, 0);
     }

     for (int i = 1; i < n; ++i)
          printf("%d ", ans[i]);
     printf("%d\n", ans[n]);
}

int main()
{
     freopen("tree.in", "r", stdin);
     freopen("tree.out", "w", stdout);
     int T;
     cin >> T;
     while (T--)
     {
          work();
     }
     return 0;
     fclose(stdin);
     fclose(stdout);
}


 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章