A. Misha and Forest
題目鏈接
http://codeforces.com/contest/504/problem/A
題目大意
給你一個無向森林裏每個結點的度數,以及每個結點相連的點的編號的亦或和。要你求出這個無向森林裏的每條邊。
思路
由於是無向森林,初始時一定有度數爲1的點,而且度數爲1的點的亦或和就是唯一的與它相連的點的編號。而刪去這個度數爲1的點後,整個圖還是一個無向森林。
於是我們可以不斷重複上述操作,類似拓撲排序,用一個隊列維護當前要刪除的度數爲1的點,如此反覆即可得到答案。
但是還有很多細節需要注意,此題真的是個FST的好題。。。
代碼
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>
#include <map>
#define MAXV (1<<17)
using namespace std;
typedef pair<int,int> pr;
vector<pr>sol;
map<pr,bool>mp;
int degree[MAXV],xorsum[MAXV],n,m;
int q[MAXV],h=0,t=0;
bool vis[1<<17];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d%d",°ree[i],&xorsum[i]);
if(degree[i]==1)
{
q[t++]=i;
vis[i]=true;
}
}
while(h<t)
{
int u=q[h++];
int v=xorsum[u];
if(!degree[u]) continue; //!!!!!
degree[u]--;
degree[v]--;
xorsum[v]^=u;
if(!mp.count(make_pair(u,v))&&!mp.count(make_pair(v,u))) sol.push_back(make_pair(u,v));
mp[make_pair(u,v)]=true;
if(degree[v]==1&&!vis[v])
{
q[t++]=v;
vis[v]=true;
}
}
printf("%d\n",sol.size());
for(int i=0;i<sol.size();i++)
printf("%d %d\n",sol[i].first,sol[i].second);
return 0;
}
B. Misha and Permutations Summation
題目鏈接
http://codeforces.com/contest/504/problem/B
題目大意
給你兩個長度爲n的排列a與b,定義fA爲排列a的字典序編號,求編號爲(fA+fB)mod n!的排列,字典序編號是從0到n-1,排列裏的數字範圍爲[0,n-1]
思路
對於一個長度爲
其中
我們可以從左到右掃一遍整個全排列,用一個平衡樹
然後我們把排列
第一次嘗試用pbds庫裏的平衡樹做題,感覺非常爽!
代碼
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define MAXN 210000
using namespace std;
using namespace __gnu_pbds;
typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update> bst;
int n,permua[MAXN],permub[MAXN],prec[MAXN]; //prec[i]=答案排列裏,在第i個數字左邊,但是又比第i個數字小d數字個數
bst A,B,C; //平衡樹A和B裏維護的是從左到右掃排列序列時,尚未被加入的數字集合
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&permua[i]);
for(int i=0;i<n;i++) scanf("%d",&permub[i]);
for(int i=0;i<n;i++)
{
A.insert(i);
B.insert(i);
C.insert(i);
}
for(int i=0;i<n;i++)
{
int a=A.order_of_key(permua[i]); //找出ai有多少個比它小的但是在其左邊又尚未被用過的數字
int b=B.order_of_key(permub[i]);
prec[i]=a+b;
A.erase(permua[i]);
B.erase(permub[i]);
}
for(int i=n-1;i>=1;i--) //對變進制數prec[]進行進位
{
prec[i-1]+=prec[i]/(n-i);
prec[i]%=(n-i);
}
prec[0]%=n;
for(int i=0;i<n;i++)
{
int tmp=*C.find_by_order(prec[i]); //在平衡樹C裏查詢第prec[i]+1大的數,就是答案排列裏第i位的數
printf("%d ",tmp);
C.erase(tmp);
}
printf("\n");
return 0;
}
C. Misha and Palindrome Degree
題目鏈接
http://codeforces.com/contest/504/problem/C
題目大意
給你一個字符串,定義一個區間
思路
首先我們可以把這個字符串左右兩邊相同的前綴和後綴去掉,這樣的話,剩下的字符串裏沒有相同的前綴與後綴。
不妨設剩下的字符串在原串裏的區間爲
由於這個區間的合法性是單調的(即
我們可以在
1.i和n-i+1都在操作區間內,就無所謂了,先把這一對位置放到一邊暫時不管
2.i在操作區間內,但是n-i+1不在操作區間內,則用一個s[n-i+1]的數字放在i位置上
3.n-i+1在操作區間內,但是i不在操作區間內,則用一個s[i]的數字放在n-i+1位置上
4.i和n-i+1均不在操作區間內,則要求s[i]必須和s[n-i+1]相同
如果在掃描過程中發現有數字不夠的情況的話,就可以直接判定這個操作區間是無效的。
那麼操作完了以後,對於1剩下的點對而言,顯然是一定能隨便找出2個相同的數字填滿他們的。
實際上覆蓋了
但是答案是小於這兩種操作區間個數之和的,因爲兩種操作區間的個數裏有相同的部分,就是同時覆蓋了
代碼
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 110000
using namespace std;
typedef long long int LL;
int n,s[MAXN],L,R;
int cnt[MAXN]; //cnt[i]=數字i的出現次數
int tmpcnt[MAXN]; //tmpcnt[i]=數字i在操作區間裏的出現次數
bool check(int r) //操作區間爲[L,r]是否可行
{
memset(tmpcnt,0,sizeof(tmpcnt));
for(int i=L;i<=r;i++) tmpcnt[s[i]]++;
for(int i=1;i<n-i+1;i++)
{
bool LinRange=(i>=L&&i<=r); //true表明i是在操作區間內的
bool RinRange=((n-i+1)>=L&&(n-i+1)<=r); //true表明n-i+1是在操作區間內的
if(LinRange&&RinRange) continue; //i和n-i+1都在操作區間內,就無所謂了
else if(LinRange) //i在操作區間內,但是n-i+1不在操作區間內,則用一個s[n-i+1]的數字放在i位置上
{
if(--tmpcnt[s[n-i+1]]<0) return false;
}
else if(RinRange) //n-i+1在操作區間內,但是i不在操作區間內,則用一個s[i]的數字放在n-i+1位置上
{
if(--tmpcnt[s[i]]<0) return false;
}
else //i和n-i+1均不在操作區間內,則要求s[i]必須和s[n-i+1]相同
{
if(s[i]!=s[n-i+1])
return false;
}
}
return true;
}
LL calc() //假設操作區間爲[l,r],l<=L,求這樣的區間個數
{
int lowerBound=L,upperBound=n,ans=-1; //求出r的最小值ans
while(lowerBound<=upperBound)
{
int mid=(lowerBound+upperBound)>>1;
if(check(mid))
{
ans=mid;
upperBound=mid-1;
}
else lowerBound=mid+1;
}
return (LL)(n-ans+1)*L; //r的取值在[ans,n]範圍內,l的取值在[1,L]範圍內,乘法原理得到答案
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&s[i]);
L=1,R=n;
while(L<=R)
{
if(s[L]==s[R])
{
L++;
R--;
}
else break;
}
if(L>R)
{
printf("%I64d\n",(LL)n*(LL)(n-1)/2+n);
return 0;
}
int numOfOdd=0; //出現次數爲奇數次的數字個數
for(int i=L;i<=R;i++)
cnt[s[i]]++;
for(int i=0;i<MAXN;i++)
if(cnt[i]&1)
numOfOdd++;
if(numOfOdd>1)
{
printf("0\n");
return 0;
}
LL ans=0;
ans+=calc();
reverse(s+1,s+n+1);
ans+=calc();
ans-=(LL)L*L;
printf("%I64d\n",ans);
return 0;
}