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\)
可以發現,這個圖很簡單,右側的一個點會連著多個左側的點,左側的點只會連著一個右側的點 .
考慮將右側的點每個選擇一個左側的點相連,因此可以最大化 \(b_i=a_i\) 的個數 .
唯一不合法的情況是 \(b_i=i\) .
如果剩下一個及以上左側&右側節點,那麼必定可以通過某種排列使得 \(b_i\not=i\) .
那麼,唯一不合法的情況就是隻剩下左側&右側節點,其中左側和右側都為 \(i\) .
考慮什麼時候會出現這種情況.
發現,只有當右側有連邊的節點個數為 \(n-1\) 時才有可能出現,並且其中必定有一個節點度數為 \(2\)
此時,可以將 \(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 兩題怎麼都是分類討論的題,但是不僅考驗思維嚴謹性,還有程式碼能力.