2024“釘耙程式設計”中國大學生演算法設計超級聯賽(6)

Luckyblock發表於2024-08-06

目錄
  • 寫在前面
  • 1003
  • 1004
  • 1001
  • 1005
  • 1007
  • 1011
  • 1002
  • 1008
  • 寫在最後

寫在前面

補提地址:https://acm.hdu.edu.cn/listproblem.php?vol=65,7494~7504

以下按個人向難度排序。

前期超級順利的一場,雙人雙開環節僅持續 10min 即結束,然而二段雙人雙開環節一直到最後都沒結束呃呃後面三題都會都沒調出來呃呃

1003

簽到。

直接模擬每次鋪的鐵路位置和當前朝向即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define pr std::pair
#define mp std::make_pair
#define LL long long
int ex[4] = {-1, 0, 1, 0};
int ey[4] = {0, -1, 0, 1};
//=============================================================
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int n; std::cin >> n;
    std::string s; std::cin >> s;
    std::map <pr<int,int>, bool> vis;
    int x = 0, y = 0, d = 0;
    int ans = 1;
    for (auto ch: s) {
      x = x + ex[d], y = y + ey[d];
      if (vis[mp(x, y)] == 1) ans = -1;
      vis[mp(x, y)] = 1;
      if (ch == 'L') {
        d = (d + 1 + 4) % 4;
      } else if (ch == 'R') {
        d = (d - 1 + 4) % 4;
      } else {
        continue;
      }
    }
    if ((x != 0 || y != 0 || d != 0) && ans != -1) ans = 0;
    std::cout << ans << "\n";
  }
  return 0;
}
/*
1
4
RRRR

1
4
RRRS
*/

1004

模擬。

不在任何上課時間犯困或睡大覺,等價於:

  • 所有睡大覺時間和上課時間沒有交集;
  • 所有上課區間均被某個個清醒時間完全包含。

於是列舉所有睡大覺時間判斷是否有交集,再列舉所有清醒時間並大力列舉標記區間內的上課區間,最後檢查是否所有上課區間均被標記即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define int long long
const int kN = 5e5 + 10;
const int kInf = 2e9;
//=============================================================
int n, m;
int tag[kN], b[kN], e[kN];
//=============================================================
//=============================================================
signed main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m;
    for (int i = 1; i <= n; ++ i) std::cin >> b[i] >> e[i];
    for (int i = 1; i <= n; ++ i) tag[i] = 0;


    b[0] = -kInf, b[n + 1] = kInf, e[n + 1] = kInf;
    int no = 0;
    for (int i = 1, p = 1; i <= m; ++ i) {
      int s, t; std::cin >> s >> t;
      int lst = std::lower_bound(e, e + n + 1, s) - e;
      if (b[lst] >= s) -- lst;
      if (b[lst + 1] < t) no = 1;

      int tt = t + 2ll * (t - s);
      while (p <= n && b[p] < t) ++ p;
      while (p <= n && e[p] <= tt) tag[p] = 1, ++ p;
    }
    for (int i = 1; i <= n; ++ i) if (!tag[i]) no = 1;

    std::cout << (no ? "No" : "Yes") << "\n";
  }
  return 0;
}

1001

特判,結論。

dztlb 大神秒了,我看都沒看。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int t,n,head[N],du[N],tot;
struct node{
	int to,nxt;
}e[N<<1];
void add(int u,int v){
	e[++tot].to=v,e[tot].nxt=head[u],head[u]=tot;
}
int cnt[N];
signed main(){
	cin>>t;
	while(t--){
		cin>>n;
		tot=0;
		cnt[0]=0;
		int num=n-1;
		for(int i=1;i<=n;++i){
			head[i]=0,du[i]=0;
			cnt[i]=0;
		}
		for(int i=1,u,v;i<n;++i){
			cin>>u>>v;
			add(u,v),add(v,u);
			du[u]++,du[v]++;
		}
		for(int i=1;i<=n;++i){
			++cnt[du[i]];
		}
		bool fl=0;
		for(int i=1;i<=n;++i){
			--cnt[du[i]];
			int tmp=num;
			for(int j=head[i];j;j=e[j].nxt){
				int v=e[j].to;
				tmp--;
				--cnt[du[v]];
				++cnt[du[v]-1];
			}
			if(n-1-tmp!=2){
//				puts("No");
			}else{
				int tt=0;
				tt+=cnt[1];
				tt+=cnt[0];
				if(cnt[0]==2){
					fl=1;
					
				}
				if(cnt[0]==1){
					if(n-1-tt<=1){
						fl=1;
					}
				}
				if(cnt[0]==0){
					if(n-1-tt<=1) fl=1;
					else if(n-1-tt<=2){
						bool hh=0;
//						cout<<i<<endl;
						for(int j=head[i];j;j=e[j].nxt){
							int v=e[j].to;
							if(du[v]==2){
								for(int k=head[v];k;k=e[k].nxt){
									int vv=e[k].to;
									if(vv==i) continue;
									if(du[vv]==1){
										hh=1;
									}
								}
							}
						}
						if(!hh){ fl=1;
//						cout<<"!!!!\n";
						}
					}
				}
			}
			
			++cnt[du[i]];
			for(int j=head[i];j;j=e[j].nxt){
				int v=e[j].to;
				++cnt[du[v]];
				--cnt[du[v]-1];
			}
		}
		if(fl){
			puts("Yes");
		}else{
			puts("No");
		}
	}
	
	return 0;
}
/*
3
3
1 2
2 3
4
1 2
1 3
1 4
9
1 2
2 3
1 4
4 5
5 6
5 7
5 8
8 9
*/

1005

狀壓 DP。

三進位制狀壓 DP,比較板沒啥可說的。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=100005;
int T,n,k,mod;
char s[505][15]; 
int a[2][60005];
bool fl[60006];
int now[60005],tot;
int poww[12];
int b[60006],cnt;
int p;
signed main(){
	poww[0]=1;
	for(int i=1;i<=10;++i){
		poww[i]=poww[i-1]*3;
	}
	cin>>T;
	while(T--){
		tot=0;
		cin>>n>>k>>mod;
		for(int i=0;i<60000;++i){
			a[0][i]=0,a[1][i]=0;
			b[i]=0;
			fl[i]=0;
		}
		p=0;
		for(int i=1;i<=n;++i){
			scanf("%s",s[i]+1);
		}
		a[p][0]=1;
		fl[0]=1;
		++tot;
		now[tot]=0;
		for(int i=1;i<=n;++i){
			cnt=0;
			
			for(int o=1;o<=tot;++o){
				int pos=now[o];
				a[p^1][pos]=a[p][pos];
			}
			
			for(int o=1;o<=tot;++o){
				int pos=now[o];
				int pre=pos;
				for(int j=1;j<=k;++j){
					int c=(pos%poww[j])/poww[j-1];
					if(s[i][k-j+1]=='+'){
						//2-0 0-1 1-2
						 pos-=c*poww[j-1];
						 ++c;
						 c%=3;
						 pos+=c*poww[j-1];
					}else if(s[i][k-j+1]=='-'){
						//0-2 1-0 2-1
						 pos-=c*poww[j-1];
						 --c;
						 if(c==-1) c+=3;
						 pos+=c*poww[j-1];
					}
				}
				if(fl[pos]==0){
					++cnt;
					b[cnt]=pos;
					fl[pos]=1;
				}
				a[p^1][pos]+=a[p][pre]; a[p^1][pos]%=mod;
			}
			for(int o=1;o<=cnt;++o) now[++tot]=b[o];
			p^=1;
		}
		sort(now+1,now+1+tot);
		for(int i=1;i<=tot;++i){
			for(int j=k;j>=1;--j){
				int c=(now[i]%poww[j])/poww[j-1];
				if(c==0) cout<<'A';
				if(c==1) cout<<'B';
				if(c==2) cout<<'C';
			}
			cout<<' ';
			cout<<a[p][now[i]]%mod<<'\n';
		}
	}
	return 0;
}
/*
*/

1007

樹形 DP,暴力。

見過這個套路於是想了下就秒了,過得比 1005 還早。

所有點權值均不同,一個顯然的想法是考慮對於 \(0\le i\le n-1\),列舉由權值 \(0\sim i\) 構成的連通子圖 \(T_i\),計數有多少連通子圖 \(\operatorname{cnt}_i\) 包含它們,則這些連通子圖的 MEX 至少為 \(i+1\)。顯然包含連通子圖 \(T_i\) 的所有連通子圖一定包含連通子圖 \(T_0\sim T_{i - 1}\),則容易發現 MEX 恰好\(i+1\) 的連通子圖數即 \(\operatorname{cnt}_i - \operatorname{cnt}_{i + 1}\),則答案即為:

\[\sum_{0\le i\le n - 1} (i+1)\times (\operatorname{cnt}_i - \operatorname{cnt}_{i + 1}) = \sum_{0\le i\le n - 1} \operatorname{cnt}_i \]

於是僅需考慮如何求得 \(\operatorname{cnt}_i\) 即可,這相當於限定了包含了權值為 \(0\sim i\) 的最小的連通子圖上的點必選的構成連通子圖的方案數。

首先考慮僅限定一個點必選的方案數 \(\operatorname{cnt}_0\),以 0 為根考慮,發現很容易使用樹形 DP 解決。

記狀態 \(f_{u}\) 表示根節點 \(u\) 可選可不選時使用以 \(u\) 為根的子樹構成 \(u\) 為根的連通子圖的方案數,狀態 \(g_{u}\) 表示限定根節點 \(u\) 必選時使用以 \(u\) 為根的子樹構成 \(u\) 為根連通子圖的方案數。保證連通子圖以 \(u\) 為根,則實際上 \(f_{u}\) 僅比 \(g_{u}\) 多出了所有節點均不選擇的方案。再對於每個節點考慮子節點的子樹的形態,則有顯然的轉移方程:

\[\begin{cases} g_u = \prod\limits_{v\in \operatorname{son}_u} f_{v}\\ f_{u} = g_{u} +1 \end{cases}\]

記無任何限制時連通子圖方案數為 \(\operatorname{cnt}_{-1}\),則有 \(\operatorname{cnt}_{-1} = f_{0}\)\(\operatorname{cnt}_0 = g_{0}\)

然後考慮如何使用 \(\operatorname{cnt}_{i - 1}\) 求得 \(\operatorname{cnt}_i\)。發現從 \(T_{i - 1}\) 變為 \(T_{i}\) 時,實際上可以看做每次新增了一條從根 0 到 \(i\) 的路徑 \(0\rightarrow i\) 必選,於是僅需考慮這條路徑上新增的不在 \(T_{i-1}\) 中的節點構成的鏈 \(i'\rightarrow i\) 上各節點必選的影響。

按照上述 DP 陣列的定義容易發現,按照 \(i'\rightarrow i\) 的順序每次新增一個必選的節點 \(u\) 時,相當於令 \(\operatorname{cnt}:= \operatorname{cnt} \div f_{u} \times g_u\),又有 \(g_{u}=\prod f_{v}\),新增節點的順序實際上是隨意的。於是考慮在列舉 \(i\) 過程中標記所有已經在 \(T_i\) 中的節點,每次更新 \(i\) 時直接從 \(i\) 暴力上跳到第一個被標記的節點,邊跳邊標記並更新 \(\operatorname{cnt}\) 即可由 \(\operatorname{cnt}_{i - 1}\) 得到 \(\operatorname{cnt}_i\)

每個節點一定僅會被標記一次,總時間複雜度 \(O(n)\) 級別。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
const LL p = 998244353;
//=============================================================
int n, a[kN], pos[kN];
int fa[kN], edgenum, head[kN], v[kN << 1], ne[kN << 1];
bool tag[kN];
LL ans, cnt, f[kN], g[kN];
//=============================================================
void addedge(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
LL qpow(LL x_, LL y_) {
  LL ret = 1;
  while (y_) {
    if (y_ & 1) ret = ret * x_ % p;
    x_ = x_ * x_ % p, y_ >>= 1ll;
  }
  return ret;
}
void dfs(int u_, int fa_) {
  fa[u_] = fa_;
  f[u_] = g[u_] = 1;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    dfs(v_, u_);
    g[u_] = g[u_] * f[v_] % p;
  }
  f[u_] = (g[u_] + 1) % p;
}
void jump(int u_) {
  while (u_ && !tag[u_]) {
    cnt = cnt * qpow(f[u_], p - 2) % p * g[u_] % p;
    tag[u_] = 1;
    u_ = fa[u_];
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    edgenum = 0;
    for (int i = 1; i <= n; ++ i) head[i] = tag[i] = 0;
    for (int i = 1; i <= n; ++ i) {
      std::cin >> a[i];
      pos[a[i]] = i;
    }
    for (int i = 1; i < n; ++ i) {
      int u_, v_; std::cin >> u_ >> v_;
      addedge(u_, v_), addedge(v_, u_);
    }
    dfs(pos[0], 0);

    ans = 0, cnt = f[pos[0]];
    for (int i = 0; i < n; ++ i) {
      jump(pos[i]);
      ans = (ans + cnt) % p;
    }
    std::cout << ans << "\n";
  }
  return 0;
}
/*
1
6
0 1 2 3 4 5
1 2
1 3
3 4
3 5
2 6
*/

1011

1002

1008

寫在最後

學到了什麼:

  • 1007:首先考慮簡單子問題,並由子問題遞推。

然後是日常夾帶私貨環節:

相關文章