解法:
由boruvka算法我们受到启发,两个连通块(也是最小生成树)直接连一条可以连的权值最小的边一定构成最小生成树。先预处理每个节点到根节点的异或和dis[i],再从二进制高位开始,分治计算,把该数位为1的当一个联通块,为0的一个连通块,这样该数位1的贡献只有一个,保证最小合并后也是最小生成树。当对两个连通块进行连边时,用字典树维护,求出联通两个连通块的最小边权,加上边权即可
#include<bits/stdc++.h> #define pai pair<int,int> #define ll long long using namespace std; vector<pai> G[100005]; int dis[100005]; int tr[3000005][2]; ll ans=0; int tot; void dfs(int u,int fa) { for(auto T:G[u]) { if(T.first==fa) continue; dis[T.first]=dis[u]^T.second; dfs(T.first,u); } } void insert(int x) { int p=0; for(int i=29;i>=0;i--) { int d=(x>>i)&1; if(tr[p][d]==0) { tr[p][d]=++tot; } p=tr[p][d]; } } int qu(int x) { int p=0; int res=0; for(int i=29;i>=0;i--) { int d=(x>>i)&1; if(tr[p][d]) { p=tr[p][d]; } else { res|=(1<<i); p=tr[p][d^1]; } } return res; } void dfs1(int l,int r,int dep) { if(l>r||dep<0) return ; int mid=l-1; while(mid+1<=r&&(dis[mid+1]&(1<<dep))==0) mid++; dfs1(l,mid,dep-1); //分治 解决子问题 dfs1(mid+1,r,dep-1); if(mid==r||mid==l-1) return ;//!! int tmp=2e9; for(int i=l;i<=mid;i++) insert(dis[i]); for(int i=mid+1;i<=r;i++) { tmp=min(tmp,qu(dis[i])); } ans+=1ll*tmp; for(int i=0;i<=tot;i++) tr[i][0]=tr[i][1]=0; tot=0; } int main() { int n; scanf("%d",&n); for(int i=0;i<n-1;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); u++; v++; G[u].push_back(make_pair(v,w)); G[v].push_back(make_pair(u,w)); } dfs(1,0); sort(dis+1,dis+1+n); dfs1(1,n,29); printf("%lld\n",ans); }
解法:
观察可发现:
规律1:连续多次使用操作1,相当于将前n-1个数不断内循环调换
规律2:连续多次使用操作2,相当于将n个数不断内循环调换
规律3:多次使用操作1后,再使用操作2,将a[0]放去末尾 ,相当于,取前n-1中的任一个数放到a[n-1]
规律3可得:通过这两种操作的组合,可以将任意一个数放到任意一个位置
那么题目可转化为求:通过最少的组合操作调换,使得目标序列sorted
枚举n个最长上升子序列的长度取max
答案就是总长度减去max ans=n-max
#include<bits/stdc++.h> using namespace std; int n; int a[505]; int gao(int i) { vector<int> v; for(int j=0;j<n;j++) { int jj=lower_bound(v.begin(),v.end(),a[(i+j)%n])-v.begin(); if(jj!=v.size())v[jj]=a[(i+j)%n]; else v.push_back(a[(i+j)%n]); } return (int)v.size() ; } int main() { scanf("%d",&n); for(int i=0;i<n;i++) { scanf("%d",&a[i]); } int ans=0; for(int i=0;i<n;i++) { ans=max(ans,gao(i)); } printf("%d\n",n-ans); return 0; }
题意:给定置换规则p数组,求最多有多少不同的序列可通过多次置换变成序列
解法:在置换规则中一定存在多个置换环,即环内数的位置只可能在该环内循环变化,置换周期为环内元素个数
那么答案即为所有置换环大小的lcm
我是用的java大数类BigInteger
import java.io.IOException; import java.math.BigInteger; import java.util.Scanner; public class Main { static int maxn = 200010; static int[] a=new int[maxn]; static int[] vis=new int[maxn]; static int[] to=new int[maxn]; static BigInteger[] b = new BigInteger[maxn]; static int tot=0; static BigInteger num=BigInteger.ONE; public static void main(String[] args) throws IOException { int n; Scanner in; Scanner sc = new Scanner(System.in); n=sc.nextInt(); for(int i=0;i<n;i++) { num.multiply(BigInteger.TEN); } for(int i=1;i<=n;i++) { vis[i]=0; a[i]=sc.nextInt(); to[i] = a[i]; } int c = 0; for(int i=1;i<=n;i++) { tot = 0; if(vis[i]==0) { int now=i; while(vis[now]==0) { vis[now]=1; now=a[now]; tot++; } b[++c] =BigInteger.valueOf(tot); } } BigInteger gcd=b[1],lcm=b[1]; for(int i=2;i<=c;i++) { gcd=lcm.gcd(b[i]); lcm=lcm.multiply(b[i]).divide(gcd); } lcm.mod(num); System.out.println(lcm.toString()); } }
C++ 大数模板:
struct bign{ int d[maxn], len; void clean() { while(len > 1 && !d[len-1]) len--; } bign() { memset(d, 0, sizeof(d)); len = 1; } bign(int num) { *this = num; } bign(char* num) { *this = num; } bign operator = (const char* num){ memset(d, 0, sizeof(d)); len = strlen(num); for(int i = 0; i < len; i++) d[i] = num[len-1-i] - '0'; clean(); return *this; } bign operator = (int num){ char s[20]; sprintf(s, "%d", num); *this = s; return *this; } bign operator + (const bign& b){ bign c = *this; int i; for (i = 0; i < b.len; i++){ c.d[i] += b.d[i]; if (c.d[i] > 9) c.d[i]%=10, c.d[i+1]++; } while (c.d[i] > 9) c.d[i++]%=10, c.d[i]++; c.len = max(len, b.len); if (c.d[i] && c.len <= i) c.len = i+1; return c; } bign operator - (const bign& b){ bign c = *this; int i; for (i = 0; i < b.len; i++){ c.d[i] -= b.d[i]; if (c.d[i] < 0) c.d[i]+=10, c.d[i+1]--; } while (c.d[i] < 0) c.d[i++]+=10, c.d[i]--; c.clean(); return c; } bign operator * (const bign& b)const{ int i, j; bign c; c.len = len + b.len; for(j = 0; j < b.len; j++) for(i = 0; i < len; i++) c.d[i+j] += d[i] * b.d[j]; for(i = 0; i < c.len-1; i++) c.d[i+1] += c.d[i]/10, c.d[i] %= 10; c.clean(); return c; } bign operator / (const bign& b){ int i, j; bign c = *this, a = 0; for (i = len - 1; i >= 0; i--) { a = a*10 + d[i]; for (j = 0; j < 10; j++) if (a < b*(j+1)) break; c.d[i] = j; a = a - b*j; } c.clean(); return c; } bign operator % (const bign& b){ int i, j; bign a = 0; for (i = len - 1; i >= 0; i--) { a = a*10 + d[i]; for (j = 0; j < 10; j++) if (a < b*(j+1)) break; a = a - b*j; } return a; } bign operator += (const bign& b){ *this = *this + b; return *this; } bool operator <(const bign& b) const{ if(len != b.len) return len < b.len; for(int i = len-1; i >= 0; i--) if(d[i] != b.d[i]) return d[i] < b.d[i]; return false; } bool operator >(const bign& b) const{return b < *this;} bool operator<=(const bign& b) const{return !(b < *this);} bool operator>=(const bign& b) const{return !(*this < b);} bool operator!=(const bign& b) const{return b < *this || *this < b;} bool operator==(const bign& b) const{return !(b < *this) && !(b > *this);} string str() const{ char s[maxn]={}; for(int i = 0; i < len; i++) s[len-1-i] = d[i]+'0'; return s; } }; istream& operator >> (istream& in, bign& x) { string s; in >> s; x = s.c_str(); return in; } ostream& operator << (ostream& out, const bign& x) { out << x.str(); return out; }