1. 程式人生 > 其它 >codeforces round #733

codeforces round #733

D - Secret Santa

\(n\) 個人,第 \(i\) 個人想給第 \(a_i\) 個人送禮物.

每個人都要恰好收到 1 份禮物 ,每個人不能給自己送東西.

令第 \(i\) 個人最終把禮物送給了 \(b_i\) .

求一種安排人送禮物的方法使得 \(b_i=a_i\) 的數量最多.

\(1\leq t\leq 10^5,\ 1\leq n\leq 2\cdot 10^5,\ \sum n\leq 2\cdot 10^5,\ 1\leq a_i\leq n,\ a_i\not=i\)

首先,對於每個人建兩個點 \(i_0,i_1\) ,分別表示送禮物和收禮物.

對於 \(a_i\)

,從 \(i_0\)\({a_i}_1\) 連邊.

可以發現,這個圖很簡單,右側的一個點會連著多個左側的點,左側的點只會連著一個右側的點 .

考慮將右側的點每個選擇一個左側的點相連,因此可以最大化 \(b_i=a_i\) 的個數 .

唯一不合法的情況是 \(b_i=i\) .

如果剩下一個及以上左側&右側節點,那麼必定可以通過某種排列使得 \(b_i\not=i\) .

那麼,唯一不合法的情況就是隻剩下左側&右側節點,其中左側和右側都為 \(i\) .

考慮什麼時候會出現這種情況.

發現,只有當右側有連邊的節點個數為 \(n-1\) 時才有可能出現,並且其中必定有一個節點度數為 \(2\)

而且度數為 \(2\) 的右側節點與 \(i\) 有連邊.

此時,可以將 \(i\) 連向 \(a_i\) ,剩下另一個節點,其他度數為 \(1\) 的節點均滿足. 最後將剩下的點連向 \(i\) .

那麼,唯一可能不合法的情況都可以被處理.

對於剩下一個以上的點,找到左側和右側都出現的點,如果有兩個以上,則可以順移一位;否則,就只有一個相同的點,帶入到所有的點中,順移一位 . 此時,所有的點都可合法.

題解中給了一種圖論的方法,自己暫時還沒有想出來.

時間複雜度 :\(O(n)\)

空間複雜度 :\(O(n)\)

第一次提交 : Accept

code

#include<bits/stdc++.h>
using namespace std;
int t;
int n,a[200010],b[200010];
bool vis[200010],ok[200010];
vector<int>g[200010];
void solve(){
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i];
		a[i]--;
		g[a[i]].push_back(i);
	}
	int sum=0;
	for(int i=0;i<n;i++)if((int)g[i].size()>0)sum++;
	if(sum==n-1){
		int id;
		for(int i=0;i<n;i++)if((int)g[i].size()==0)id=i;
		b[id]=a[id];
		vis[a[id]]=ok[id]=true;
		for(int i=0;i<n;i++)if(i!=id){
			if(!vis[a[i]]){
				b[i]=a[i];
				ok[i]=vis[a[i]]=true;
			}
		}
		for(int i=0;i<n;i++)if(!ok[i]){
			for(int j=0;j<n;j++)if(!vis[j]){
				b[i]=j;
			}
		}
	}
	else{
		for(int i=0;i<n;i++){
			for(int j=0;j<(int)g[i].size();j++){
				int x=g[i][j];
				b[x]=i;
				ok[x]=true;
				vis[i]=true;
				break;
			}
		}
		vector<int>v;
		for(int i=0;i<n;i++){
			if(ok[i]&&vis[i])continue;
			if(ok[i]==false&&vis[i]==false)v.push_back(i);
		}
		if((int)v.size()!=1){
			for(int i=0;i<(int)v.size();i++){
				b[v[i]]=v[(i+1)%(int)v.size()];
				ok[v[i]]=vis[v[(i+1)%(int)v.size()]]=true;
			}
			vector<int>v1,v2;
			for(int i=0;i<n;i++){
				if(ok[i]==false)v1.push_back(i);
				if(vis[i]==false)v2.push_back(i);
			}
			for(int i=0;i<(int)v1.size();i++)b[v1[i]]=v2[i];
		}
		else{
			vector<int>v1,v2;
			v1.push_back(v[0]);
			v2.push_back(v[0]);
			for(int i=0;i<n;i++){
				if(ok[i]==false&&vis[i]==false)continue;
				if(ok[i]==false)v1.push_back(i);
				if(vis[i]==false)v2.push_back(i);
			}
			for(int i=0;i<(int)v1.size();i++)b[v1[i]]=v2[(i+1)%(int)v2.size()];
		}
	}
	int ans=0;
	for(int i=0;i<n;i++)if(b[i]==a[i])ans++;
	cout<<ans<<endl;
	for(int i=0;i<n;i++)cout<<b[i]+1<<" ";cout<<endl;
	for(int i=0;i<n;i++)ok[i]=vis[i]=false;
	for(int i=0;i<n;i++)g[i].clear();
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
E - Minimax

字串的字首陣列的第 \(i\) 個代表從第 \(i\) 位開始,可以和 \(s\) 的字首匹配的最長長度.

給出字串 \(s\) .

求重構字串 \(s\) 使得 \(s\) 字首陣列的最大值最小,要求輸出字典序最小的一組解.

\(1\leq t\leq 10^5\)

\(1\leq |s|\leq 10^5,\ \sum|s|\leq 10^5\)

首先考慮如何構造使得字首陣列的最大值最小,分情況考慮.

令字元 \(c\) 的出現次數為 \(cnt(c)\) .

  • \(s\) 中所有字元相同. 此時無法改變,直接輸出.

  • \(s\) 中存在兩個即以上不同字元.

    • 存在一種字元 \(c\)\(cnt(c)=1\)

      字串首位 \(c\) ,剩餘位置字元隨便. 最大值為 \(0\) .

    • 對於任意字元 \(c\) , \(cnt(c)>1\) 或者 \(cnt(c)=0\)

      輸出其中任意字元 \(c\) ,剩餘位置皆放到字串的尾部,剩餘位置隨便. 最大值為 \(1\).

現在發現,字首陣列的最小值最大為 \(1\) .

接下來就是要求字典序最小,本體的難度也在於此,情況也更多了.

  • \(s\) 中所有的字元相同. 直接輸出

  • \(s\) 中存在兩個及以上不同字元.

    • 存在一種字元 \(c\) , \(cnt(c)=1\)

      找到字典序的字元 \(c\)\(cnt(c)=1\) ,放在字串首位,剩餘位置從小到大排序填入.

    • 對於任意字元 \(c\) , \(cnt(c)>1\)\(cnt(c)=0\)

      這個可以分出不少情況. 首先考慮字串首位填入的字元要字典序儘可能小,那麼,就列舉字典序最小的 \(x\) 填入首尾. 接著,我以為必須要在第二位填入和 \(x\) 不同的字元 \(y\) . 其實是不一定的,認真閱讀樣例,發現如果開頭兩位為 \(xx\) ,在某些條件下是滿足字首陣列的最大值為 \(1\) ,並且 \(xx\) 的字典序要由於 \(xy\) . 分析出現 \(xx\) 需要滿足什麼條件,首先,對於剩餘位置來說,不可能出現連續的兩個 \(xx\),對於剩下的 \(cnt(x)-2\)\(x\) , 考慮交錯填,那麼,至少需要 \(cnt(x)-2\) 個與 \(x\) 的不同的字元才能滿足條件,否則,無法滿足. 此時可以分出第一類.

      • \(cnt(x)-2\leq n-cnt(x)\)

        \(1\) 位和第 \(2\) 位為 \(x\) ,剩餘的 \(x\) 依次填入第 \(4\) 位,第 \(6\) 位,第 \(8\) 位,\(\cdots\)

        剩下的位置按照字典序的大小依次填入

      • \(cnt(x)-2>n-cnt(x)\)

        第二位不能為 \(x\) ,那麼考慮第二位為字典序第二小的字元 \(y\),大概想一下,那麼最優的答案為 \(xyxx\cdots xz\cdots\),在 \(y\) 之後放入 \(x\),保證字典序最大,但是在最後一個 \(x\) 之後不能放入 \(y\) ,因為要保證字典序最大,此時放入字典序第三大的字元 \(z\) . 那麼,就需要 \(cnt(c)>1\) 的字元 \(3\) 種,接下來,就可以又分出兩種情況.

        • 出現的字串種類大於 \(2\) .

          按照 \(xyxx\cdots xz\cdots\) 填,剩餘的位置字典序從大到小填.

        • 出現的字串種類等於 \(2\) .

          按照 \(xyy\cdots yx\cdots x\) 填.

時間複雜度 : \(O(n)\)

空間複雜度 : \(O(n)\)

第一次提交 : Run time error on test 2

code

#include<bits/stdc++.h>
using namespace std;
int t;
int n;
string s;
int cnt[30];
int ans[100010];
void solve(){
	cin>>s;
	n=(int)s.size();
	memset(cnt,0,sizeof(cnt));
	for(int i=0;i<n;i++)ans[i]=-1;
	for(int i=0;i<n;i++)cnt[s[i]-'a']++;
	bool flag;
	flag=false;
	for(int i=0;i<26;i++)if(cnt[i]==n)flag=true;
	if(flag){
		cout<<s<<endl;
		return;
	}
	flag=false;
	for(int i=0;i<26;i++)if(cnt[i]==1)flag=true;
	if(flag){
		for(int i=0;i<26;i++)if(cnt[i]==1){
			cout<<char(i+'a');
			cnt[i]--;
			break;
		}
		for(int i=0;i<26;i++){
			for(int j=0;j<cnt[i];j++)cout<<char(i+'a');
		}
		cout<<endl;
		return;
	}
	int x;
	for(int i=0;i<26;i++){
		if(cnt[i]>0){
			x=i;
			break; 
		}
	}
	if(n-cnt[x]>=cnt[x]-2){
		ans[0]=ans[1]=x;
		cnt[x]-=2;
		for(int i=3;i<n;i+=2){
			if(cnt[x]<=0)break;
			ans[i]=x;
			cnt[x]--;
		}
		for(int i=0;i<n;i++){
			if(ans[i]==-1){
				for(int j=0;j<26;j++){
					if(cnt[j]>0){
						cnt[j]--;
						ans[i]=j;
						break;
					}
				}
			}
		}
	}
	else{
		int sum=0;
		for(int i=0;i<26;i++)if(cnt[i]>0)sum++;
		if(sum==2){
			ans[0]=x;cnt[x]--;
			for(int i=n-1;i>=0;i--){
				ans[i]=x;
				cnt[x]--;
				if(cnt[x]==0)break;
			}
			for(int i=0;i<n;i++){
				if(ans[i]==-1){
					for(int j=0;j<26;j++){
						if(cnt[j]>0){
							ans[i]=j;
							cnt[j]--;
							break;
						}
					}
				}
			}
		}
		else{
			ans[0]=x;cnt[x]--;
			int y;
			for(int i=0;i<26;i++){
				if(i!=x&&cnt[i]>0){
					y=i;
					cnt[i]--;
					ans[1]=i;
					break;	
				}
			}
			int j=2;
			for(int i=2;i<n;i++,j++){
				ans[i]=x;
				cnt[x]--;
				if(cnt[x]==0)break;
			}
			j++;
			for(int i=0;i<26;i++){
				if(i!=x&&i!=y&&cnt[i]>0){
					cnt[i]--;
					ans[j]=i;
					break;
				}
			}
			for(int i=0;i<n;i++){
				if(ans[i]==-1){
					for(int j=0;j<26;j++){
						if(cnt[j]>0){
							cnt[j]--;
							ans[i]=j;
							break;
						}
					}
				}
			}
		}
	}
	for(int i=0;i<n;i++)cout<<char(ans[i]+'a');
	cout<<endl;
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
/*inline? ll or int? size? min max?*/

Petr ‘ s code

/**
 * code generated by JHelper
 * More info: https://github.com/AlexeyDmitriev/JHelper
 * @author
 */

// Actual solution is at the bottom

#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <climits>
#include <cstdint>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <memory>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <vector>
//#include "../atcoder/all"

#define sz(v) ((int)(v).size())
#define all(v) (v).begin(),(v).end()

using namespace std;

typedef int64_t int64;
typedef pair<int, int> ii;

class EMinimaks {
 public:
  void solveOne() {
    string s;
    cin >> s;
    vector<int> cnt(26);
    for (char c : s) {
      ++cnt[c - 'a'];
    }

    auto dump = [&] {
      for (int j = 0; j < 26; ++j) {
        for (int k = 0; k < cnt[j]; ++k) {
          cout << (char) ('a' + j);
        }
      }
      cout << "\n";
    };
    int nonzero = 0;
    int first = -1;
    int second = -1;
    int third = -1;
    for (int i = 0; i < 26; ++i) {
      if (cnt[i]) {
        ++nonzero;
        if (first < 0) first = i; else if (second < 0) second = i; else if (third < 0) third = i;
      }
      if (cnt[i] == 1) {
        cout << (char) ('a' + i);
        --cnt[i];
        dump();
        return;
      }
    }
    if (nonzero == 1) {
      dump();
      return;
    }
    if (2 * (cnt[first] - 1) <= s.size()) {
      cout << (char) ('a' + first);
      --cnt[first];
      for (int j = first + 1; j < 26; ++j) {
        for (int k = 0; k < cnt[j]; ++k) {
          if (cnt[first] > 0) {
            cout << (char) ('a' + first);
            --cnt[first];
          }
          cout << (char) ('a' + j);
        }
        cnt[j] = 0;
      }
      dump();
      return;
    } else {
      cout << (char) ('a' + first);
      --cnt[first];
      cout << (char) ('a' + second);
      --cnt[second];
      if (third >= 0) {
        for (int k = 0; k < cnt[first]; ++k) {
          cout << (char) ('a' + first);
        }
        cnt[first] = 0;
        cout << (char) ('a' + third);
        --cnt[third];
        dump();
      } else {
        for (int k = 0; k < cnt[second]; ++k) {
          cout << (char) ('a' + second);
        }
        cnt[second] = 0;
        dump();
      }
      return;
    }
  }

  void solve() {
    int nt;
    cin >> nt;
    for (int it = 0; it < nt; ++it) {
      solveOne();
    }
  }
};


int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    EMinimaks solver;


    solver.solve();
    return 0;
}

tourist 出的題,de 兩題怎麼都是分類討論的題,但是不僅考驗思維嚴謹性,還有程式碼能力.