C题意:
给定n个点(标号0~n-1)的度数(就是与其邻接的点的个数)和所有与它邻接的点标号的异或和,求满足这些条件的树的边应该是怎么连的,将边输出出来
这里可以理解成拓扑排序的方式考虑,当i度数为1的时候,那么我们必然知道 i 肯定得与s[i]相连使其剩余的异或值为0
所以建立一个队列不断将度数变为1的点放进来,得到边以后,不断更新点的度数和其对应的异或和的值
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 const int N = (1<<16); 5 int n,degree[N] , s[N] , vis[N]; 6 #define pii pair<int,int> 7 vector<pii> v; 8 queue<int> q; 9 int main() 10 { 11 // freopen("a.in" , "r" , stdin); 12 scanf("%d" , &n); 13 for(int i=0 ; i<n ; i++){ 14 scanf("%d%d" , °ree[i] , &s[i]); 15 if(degree[i]==1) q.push(i); 16 } 17 while(!q.empty()){ 18 int u = q.front(); 19 q.pop(); 20 // cout<<u<<" "<<degree[u]<<" "<<s[u]<<endl; 21 if(degree[u]==0) continue; 22 degree[u]--; 23 if(!vis[s[u]]){ 24 degree[s[u]]-- , s[s[u]]^=u; 25 if(degree[s[u]]==1) q.push(s[u]) , vis[s[u]]=1; 26 } 27 else degree[s[u]]--; 28 v.push_back(make_pair(u , s[u])); 29 } 30 printf("%d\n" , v.size()); 31 for(int i=0 ; i<v.size() ; i++) 32 printf("%d %d\n" , v[i].first , v[i].second); 33 return 0; 34 }
D题意:
给定两个0~n-1的排列方式,像康托展开式那种方式计算对应排名(从0开始,也就是说排名可能是0~n!)
将两种排列的排名相加后计算新的排名%n!后的对应的排列方式
这里我们可以计算排列上每一位对应的名次,这个名次的含义是从最高位开始这个数在不曾出现的数字中的排名,这个排名可以用树状数组解决
如序列 2 1 3 0 4 对应排名 2 1 2 0 0
这里给定样例
5
2 1 3 0 4
2 0 4 3 1
可以看出来两个序列排名后是:
2 1 1 0 0
2 0 2 1 0
相当于把每一位相加就说明得到新排名是符合总和相加所得排名的(不懂可以学一下康拓展开)
就是4 1 3 1 0
那这里对应每一位来说,都不应该出现超出当前位所能承受的排名就进位,比如这里3的位置最多排名应该为2,所以变成3%3=0,前面进位1+1=2
新的排名是4 2 0 1 0
根据新的排名倒过来算出每一位上的数字,我用的是线段树处理,当然应该有更好的办法,只是我比较弱~
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 const int N = 100005; 5 int n , a[N] , cnt[N] , use[N] , use1[N]; 6 #define ll long long 7 ll ans; 8 int main() 9 { 10 // freopen("a.in" , "r" , stdin); 11 scanf("%d" , &n); 12 for(int i=1 ; i<=n ; i++){ 13 scanf("%d" , &a[i]); 14 cnt[a[i]]++; 15 } 16 17 int odd = 0; 18 for(int i=1 ; i<=n ; i++){ 19 if(cnt[i]&1) odd++; 20 } 21 if(((n&1)==0 && odd) || ((n&1)&&odd>1)){ 22 puts("0"); 23 return 0; 24 } 25 //从左右两端出发能匹配到的最大次数 26 int maxMatch = 0; 27 for(int i=1,j=n ; i<=j ; i++,j--){ 28 if(a[i]==a[j])maxMatch++; 29 else break; 30 } 31 if(maxMatch==(n+1)/2){ 32 printf("%I64d\n" , (ll)n*(n+1)/2); 33 return 0; 34 } 35 //从中间出发能匹配到的最大次数 36 int maxMid = 0; 37 for(int i=n/2 , j=(n+1)/2+1; i>0 ; i-- , j++){ 38 if(a[i]==a[j])maxMid++; 39 else break; 40 } 41 ans = 0; 42 43 //计算最右侧出发还存在值的最大位置 44 int mxRg = 0; 45 for(mxRg=1 ; mxRg<=n ; mxRg++){ 46 if(n&1 && mxRg==(n+1)/2 && cnt[a[mxRg]]&1) continue; 47 else if(n&1 && mxRg==(n+1)/2) break; 48 if(mxRg<=(n+1)/2){ 49 if(use1[a[mxRg]]+1<=cnt[a[mxRg]]/2) {use1[a[mxRg]]++;continue;} 50 else break; 51 } 52 else { 53 if(a[mxRg]==a[n-mxRg+1]) continue; 54 else break; 55 } 56 } 57 //计算找到如果没有回文重排点的右半边最小位置 58 int last = n; 59 int index=1; 60 while(last>=index){ 61 if(n&1 && last==(n+1)/2 && cnt[a[last]]&1) {last--;continue;} 62 else if(n&1 && last==(n+1)/2) break; 63 if(last<=n/2){ 64 if(maxMid) last-=maxMid; 65 break; 66 } 67 else { 68 if(last>=n-index+2 && a[last]!=a[n-last+1]) break; 69 if(use[a[last]]+1>cnt[a[last]]/2) break; 70 else use[a[last]]++ , last--; 71 } 72 } 73 if(last<index) last=index; 74 // cout<<"last: "<<last<<endl; 75 /*----------------*/ 76 if(mxRg>n) mxRg=n; 77 for(int index=1 ; index<=mxRg ; index++){ 78 int flag = index*n; 79 int val = max(n-index+1 , n-maxMatch); 80 // cout<<"first: "<<index<<" "<<last<<endl; 81 if(val == n-index+1) ans+=n-(last<index?index:last)+1; 82 else ans+=maxMatch+1;//前面如果整个序列是回文串就会直接输出答案,所以这里maxMatch不可能会超过一半 83 // cout<<index<<" "<<ans<<endl; 84 } 85 printf("%I64d\n" , ans); 86 return 0; 87 }
E题题意:
就是给定一个数字序列,任取一段区间重新排列,要是能找到一种排列方式使之后形成的整个序列是个回文序列,就说明取的这段区间是个合法区间
问有多少个合法区间
这题目开始考虑区间[l,r]合法,那么说明取[l , r'] r'>r均合法
那么只要枚举每一位l,快速找到最小的r即可用ans+=n-r+1就能算出来
将序列本身是回文的和序列永远无法变成回文特判,相信这个大家都认为是很简单的
我们找到一段从左端开始,和最右端开始的回文匹配得到能匹配到的最大值
1 //从左右两端出发能匹配到的最大次数 2 int maxMatch = 0; 3 for(int i=1,j=n ; i<=j ; i++,j--){ 4 if(a[i]==a[j])maxMatch++; 5 else break; 6 } 7 if(maxMatch==(n+1)/2){ 8 printf("%I64d\n" , (ll)n*(n+1)/2); 9 return 0; 10 }
我们每次得到一个新的l,如果这个l之前的串都已经能被回文匹配,说明那一段无论如何都取的到,那只要知道我任意匹配所能走到r的最小位置
如果没有满足全部回文匹配,那么说明最多只能从回文断开的地方取区间,总大小正好是maxMatch+1;
比如r可以从当前位置n走,在走过一半前只要判断取到的数是否超过原个数的一半即可,没超过说明合法
如果走过了一半 说明前面取到的数正好是一半,只要当前能够跟对应的位置匹配就可以取不取都无所谓了
这里我觉得还要考虑一个正好到奇数个数的中点的特判,保证那个位置是唯一出现的奇数个数的数即可
根据上述方法,再计算一个可枚举的l范围从1到k停止,也是根据上方所述一步步走:
1 //计算最右侧出发还存在值的最大位置 2 int mxRg = 0; 3 for(mxRg=1 ; mxRg<=n ; mxRg++){ 4 if(n&1 && mxRg==(n+1)/2 && cnt[a[mxRg]]&1) continue; 5 else if(n&1 && mxRg==(n+1)/2) break; 6 if(mxRg<=(n+1)/2){ 7 if(use1[a[mxRg]]+1<=cnt[a[mxRg]]/2) {use1[a[mxRg]]++;continue;} 8 else break; 9 } 10 else { 11 if(a[mxRg]==a[n-mxRg+1]) continue; 12 else break; 13 } 14 } 15 if(mxRg>n) mxRg=n;
得到那个最小的位置last后结果就很容易算了
1 for(int index=1 ; index<=mxRg ; index++){ 2 int flag = index*n; 3 int val = max(n-index+1 , n-maxMatch); 4 // cout<<"first: "<<index<<" "<<last<<endl; 5 if(val == n-index+1) ans+=n-(last<index?index:last)+1; 6 else ans+=maxMatch+1;//前面如果整个序列是回文串就会直接输出答案,所以这里maxMatch不可能会超过一半 7 // cout<<index<<" "<<ans<<endl; 8 }
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 const int N = 100005; 5 int n , a[N] , cnt[N] , use[N] , use1[N]; 6 #define ll long long 7 ll ans; 8 int main() 9 { 10 // freopen("a.in" , "r" , stdin); 11 scanf("%d" , &n); 12 int time =0; 13 for(int i=1 ; i<=n ; i++){ 14 scanf("%d" , &a[i]); 15 if(cnt[a[i]]==0) time++; 16 cnt[a[i]]++; 17 } 18 if(time==1){ 19 printf("%I64d\n" , (ll)n*(n+1)/2); 20 return 0; 21 } 22 int odd = 0; 23 for(int i=1 ; i<=n ; i++){ 24 if(cnt[i]&1) odd++; 25 } 26 if(((n&1)==0 && odd) || ((n&1)&&odd>1)){ 27 puts("0"); 28 return 0; 29 } 30 //从左右两端出发能匹配到的最大次数 31 int maxMatch = 0; 32 for(int i=1,j=n ; i<=j ; i++,j--){ 33 if(a[i]==a[j])maxMatch++; 34 else break; 35 } 36 //从中间出发能匹配到的最大次数 37 int maxMid = 0; 38 for(int i=n/2 , j=(n+1)/2+1; i>0 ; i-- , j++){ 39 if(a[i]==a[j])maxMid++; 40 else break; 41 } 42 ans = 0; 43 int last = n; 44 45 //计算最右侧出发还存在值的最大位置 46 int mxRg = 0; 47 for(mxRg=1 ; mxRg<=n ; mxRg++){ 48 if(n&1 && mxRg==(n+1)/2 && cnt[a[mxRg]]&1) continue; 49 else if(n&1 && mxRg==(n+1)/2) break; 50 if(mxRg<=(n+1)/2){ 51 if(use1[a[mxRg]]+1<=cnt[a[mxRg]]/2) {use1[a[mxRg]]++;continue;} 52 else break; 53 } 54 else { 55 if(a[mxRg]==a[n-mxRg+1]) continue; 56 else break; 57 } 58 } 59 if(mxRg>n) mxRg=n; 60 for(int index=1 ; index<=mxRg ; index++){ 61 int flag = index*n; 62 last = max(n-index+1 , n-maxMatch); 63 // cout<<"first: "<<index<<" "<<last<<endl; 64 if(last<n && last == n-index+1) cnt[a[last+1]] -= 2; 65 while(last>=index){ 66 if(n&1 && last==(n+1)/2 && cnt[a[last]]&1) {last--;continue;} 67 else if(n&1 && last==(n+1)/2) break; 68 if(last<=n/2){ 69 if(maxMid) last-=maxMid; 70 break; 71 } 72 else { 73 if(use[a[last]]<flag) use[a[last]]=flag; 74 if(last>=n-index+2 && a[last]!=a[n-last+1]) break; 75 if(use[a[last]]+1-flag>cnt[a[last]]/2) break; 76 else use[a[last]]++ , last--; 77 } 78 } 79 if(last<index) last=index; 80 // cout<<index<<": "<<last<<" "<<maxMid<<endl; 81 ans += n-last+1; 82 } 83 printf("%I64d\n" , ans); 84 return 0; 85 }