Codeforces Round #473 (Div. 2) (A-E) (水題 + 並查集 + 構造 + 分解因數 + 貪心)
A
題意
給出一個數 ,兩人每次可以選擇一個數 ,使得 。其中 且第一個人選的a應為偶數,第二個人選的應為奇數,誰不能選的時候為負。誰不能操作為負,問誰能贏。
思路
判斷 的奇偶性即可,先手偶數不改變奇偶性,後手奇數改變奇偶性,當 時遊戲結束,也就是說僅判斷 的奇偶性即可。
程式碼
int main()
{
int n;
sd(n);
if(n&1) puts("Ehab");
else puts("Mahmoud");
return 0;
}
B
題意
給出 個字串,以及他們的花費,然後將這些字串分組,每個串可以用同組的字串替換,然後給出 個目標串,問得到目標串的最小花費
思路
解決的核心在於如何記錄每個字串屬於哪個組,以及該組的最小花費。一開始讀錯題了寫了個並查集,但是這裡也還能用。有更簡單的維護辦法
程式碼
map<string, int> mp;
int fa[maxn], cost[maxn];
void init(){
rep(i, 0, maxn){
fa[i] = i;
}
}
int found(int x){
return x==fa[x]?x:(fa[x] = found(fa[x]));
}
void merg(int x, int y){ // fa[y] = x;
int fax = found(x);
int fay = found(y);
if(fax == fay) return ;
else {
cost[fax] = min(cost[fax], cost[fay]);
fa[fay] = fax;
}
}
int main()
{
ios::sync_with_stdio(false);
int n, k, m;
init();
cin>>n>>k>>m;
rep(i, 1, n+1){
string str;
cin>>str;
// debug(str);
mp[str] = i;
}
rep(i, 1, n+1){
int x;
cin>>x;
cost[i] = x;
}
rep(i, 0, k){
int num, fi, tmp;
cin>>num>>fi;
rep(i, 1, num){
cin>>tmp;
merg(fi, tmp);
}
}
ll ans = 0;
rep(i, 0, m){
string str;
cin>>str;
// debug(str);
int fas = found(mp[str]);
// debug(fas);
ans += cost[fas];
}
cout<<ans<<endl;
return 0;
}
C
題意
在樹上求一個點集,要求樹上的任意一條邊,都至少有一個端點,在這個集合內,求集合內元素的最小個數。現給出一種演算法,以1號節點為根,按奇偶分層,答案為奇偶層節點分別求和的較小值。現需要構造出 個節點的兩棵樹,第一棵不滿足演算法,第二棵滿足演算法。
思路
滿足條件的樹好構造,只需要所有節點連向 號節點,那麼 就是答案。不滿足條件的樹需要的一個條件是,答案應該同時小於奇數層結點數和偶數層節點數。經手推構造發現,當 時 ,不存在。當 時,只需要 號節點連向 其餘節點連向 即可。
程式碼
int main()
{
int n;
sd(n);
if(n <= 5) puts("-1");
else {
rep(i, 2, n+1){
printf("%d %d\n", i<5?1:2, i);
}
}
rep(i, 2, n+1)
printf("1 %d\n", i);
return 0;
}
D
題意
給出一個序列 ,需要構造一個數列 ,使得 序列的字典序大於等於 序列,且 序列中任意兩個元素兩兩互質,問滿足條件的,字典序最小的 序列是多少。
思路
這道題很開心的一點是,又學會了一種預處理質因數分解的寫法。第一種,可以對於每個質數,遍歷 中它的倍數,加到vector中。第二種,只需要記錄當前數的最小質因子,不斷轉移即可。 然後就貪心求每個數,判斷當前數是否可用,以及加入當前數後,更新可用狀態
程式碼
int fac[maxn], vis[maxn];
int a[maxn], b[maxn];
void init(){
rep(i, 2, maxn){
if(!fac[i]){
for(int j = i; j<maxn; j+=i) { // minn fac
if(!fac[j]) fac[j] = i;
}
}
}
}
bool judge(int x){
while(x > 1){
if(vis[fac[x]]) return false;
x = x / fac[x];
}
return true;
}
void push_up(int x){
while(x > 1){
vis[fac[x]] = 1;
x = x / fac[x];
}
}
int main()
{
init();
int n;
sd(n);
rep(i, 0, n) sd(a[i]);
rep(i, 0, n) {
if(judge(a[i])) {
b[i] = a[i];
push_up(a[i]);
}
else {
int now = a[i], nowp = i;;
while(!judge(now)) now++;
b[nowp++] = now;
push_up(now);
now = 2;
while(nowp < n){
while(!judge(now)) now++;
b[nowp++] = now;
push_up(now);
}
break;
}
}
rep(i, 0, n) {
printf("%d%c", b[i], i==n-1?'\n':' ');
}
return 0;
}
E
題意
給出 個點,編號為 任意兩點之間的線段權值為兩點的亦或值。
思路
每次貪心的把最小花費的邊全部連上,即二進位制只有一位不同的部分,相當於連續合併。
程式碼
int main()
{
ll n, sum = 0, bas = 1;
sld(n);
while(n > 1){
sum += (n>>1) * bas;
n = (n+1)>>1;
bas <<= 1;
}
pld(sum);
return 0;
}