20190725-20190726
异或约数和
题意简述
求 $f_1\space xor\space f_2…\space xor\space f_n$ , $f_i$ 表示 $i$ 的所有约数的异或和。
$n\leq 10^{14}$ 。
$solution:$
考虑 $xor$ 有结合律与交换律,所以考虑优化给定式子。
对于 $1-n$ 有 $x$ 这种约数的有 $[\dfrac{n}{x}]$ ,直接查约数个数是否为奇数,时间复杂度 $O(n)$。
而对于 $[\dfrac{n}{x}]$ ,当 $x\leq \sqrt{n}$ 时 $[\dfrac{n}{x}]$ 个不相同。
而当 $x>\sqrt{n}$ 时 $[\dfrac{n}{x}]$ 只有 $\sqrt{n}$ 个取值范围,所以直接暴力维护一下 $[\dfrac{n}{x}]$ 即可。
时间复杂度 $O(\sqrt{n})$ 。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define int long long using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } int n,ans; inline int Qx(int ps){ if(!ps) return 0; if(ps%4==0) return ps; if(ps%4==1) return 1; if(ps%4==2) return ps+1; if(ps%4==3) return 0; } inline int Xor(int l,int r){ return Qx(r)^Qx(l-1); } int ps; signed main(){ freopen("Xor.in","r",stdin); freopen("Xor.out","w",stdout); n=read(); ps=1; while(ps<=n){ int l=ps,r=n/((int)(n/ps)),g=n/ps; if(g%2==1) ans^=Xor(l,r); ps=r+1; }printf("%lld\n",ans);return 0; }
OR三元组
题意简述
$q$ 次询问,每次询问满足 $a_i|a_j|a_k=x,i<j<k$ 的三元组 $(i,j,k)$ 的个数。
$n,q\leq10^6,1\leq a_i,x\leq 255$ 。
$solution:$
可以现在 $O(255\times n)$ 时间内处理出 $f_{i,j}$ 表示前 $i$ 个数中有 $f_{i,j}$ 个数满足 $or\space x=x$ 。
考虑容斥,$Ans=\sum (1\space or\space -1)\dbinom{i}{3}\space(i\space or x\space =x)$ ,而正负 $1$ 直接判断二进制上含 $1$ 的个数差是奇还是偶。
正确性证明,考虑维恩图,$\dbinom{x}{3}$ 表示随机散点的个数,而最后要求的是散到最小的那块个数,因为是 $i\space or\space x=x$,所以最后散到的就是 $x$ 。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define int long long using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=100001; int dig[11],n,q,a[MAXN],f[MAXN][256]; int Q(int g){ if(g<3) return 0; return (g*(g-1)*(g-2))/6; } int Qdig(int x){ int Ans=0; while(x){ Ans+=(x&1); x/=2; }return Ans; } signed main(){ freopen("Or.in","r",stdin); freopen("Or.out","w",stdout); n=read(),q=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=1;i<=n;i++){ for(int j=0;j<=255;j++) if((a[i]&j)==a[i]) f[i][j]++; } for(int i=1;i<=n;i++) for(int j=0;j<=255;j++) f[i][j]+=f[i-1][j]; for(int i=1;i<=q;i++){ int l=read(),r=read(),x=read(),ans=0; for(int j=0;j<=x;j++){ if((x|j)==x){ int dig=Qdig(x)-Qdig(j); ans+=(dig%2?-1:1)*Q(f[r][j]-f[l-1][j]); } }printf("%lld\n",ans); } }/* 3 1 1 1 1 1 3 1 */
香农游戏
题意简述
$T$ 组询问,每次给定 $n$ 个点 $m$ 条边的图,问是否可以存在两组不相交的边集,可以是每个图均联通。
$T,n\leq 10,m\leq 100$ 。
$solution:$
考虑搜索,若存在图 $G_1,G_2$ 满足条件。
那么对于现在要搜索的边 $u,v$ ,若在 $G_1$ 中可以减少连通块,那么就选,否则不选。$G_2$ 同理,发现这样就过了。
因为边集搜索最高对于 $G$ 有 $10$ 条,所以最后的时间复杂度近似于 $O(2^{20}\times w)$ , $w$ 为一个比较小的常数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=11; int n; struct Union{ int f[MAXN],val,sta[MAXN]; void init(){val=n;for(int i=1;i<=n;i++) f[i]=i;} int find(int x){ if(f[x]==x) return x; return find(f[x]); } void merge(int x,int y){ int t1=find(x),t2=find(y); f[t2]=t1; sta[++sta[0]]=t2; val--; } void back(){ f[sta[sta[0]]]=sta[sta[0]]; sta[0]--; val++; } void debug(){ for(int i=1;i<=n;i++) printf("%d ",f[i]); printf("\n");return; } }G1,G2; int T,m; struct node{ int u,v; }x[MAXN*MAXN]; bool flag; void dfs(int ps){ if(flag) return; if(G1.val==1&&G2.val==1){flag=1;return;} if(ps==m+1) return; bool F=1; if(G1.find(x[ps].u)!=G1.find(x[ps].v)){ F=0; G1.merge(x[ps].u,x[ps].v); dfs(ps+1); G1.back(); } if(G2.find(x[ps].u)!=G2.find(x[ps].v)){ F=0; G2.merge(x[ps].u,x[ps].v); dfs(ps+1); G2.back(); } if(F) dfs(ps+1); return; } void solve(){ flag=0; n=read(),m=read();G1.init(),G2.init(); for(int i=1;i<=m;i++) x[i].u=read(),x[i].v=read(); dfs(1); if(flag) printf("Possible\n"); else printf("Impossible\n"); return; } int main(){ freopen("Game.in","r",stdin); freopen("Game.out","w",stdout); T=read(); while(T--) solve(); }
致富之路
题意简述
有 $m$ 个可供选择的左下角 , $n$ 个可供选择的右上角,问最大矩形面积。
$n,m\leq 10^5$ 。
$solution:$
考虑对于左下角的决策点,若 $x$ 相同时肯定 $y$ 最小的是最优的,而当 $x_i<x_j$ 时, $y_i>y_j$ 是更优的。
右上角决策点也如此,若 $x$ 相同时 $y$ 最大是最优的,而最后我们想看到的是 $x_i<x_j,y_i>y_j$ 的支持删除的,直接单调栈即可。
现在已经可以求出比较好的决策点,考虑右上角 $(x_1,y_1),(x_2,y_2),(x_1<x_2,y_1>y_2)$
设对于 $(x_1,y_1)$ 的最优左下决策点为 $(x,y)$ ,则对于 $(x_2,y_2)$ 的决策点 $(xx,yy)$ 一定 $x\leq xx$ ,可以画图或者推式子得到。
所以满足决策单调性,直接分治即可,时间复杂度 $O(n\log n)$ 。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<stack> #include<climits> #define LL long long using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=500001; int n,m,N,M; struct Fac{ int p,d; }f[MAXN],F[MAXN]; bool cmp1(Fac x1,Fac x2){ if(x1.p==x2.p) return x1.d<x2.d; return x1.p<x2.p; } struct Com{ int q,e; }g[MAXN],G[MAXN]; bool cmp2(Com x1,Com x2){ if(x1.q==x2.q) return x1.e>x2.e; return x1.q<x2.q; } int Mx,My,k[MAXN]; LL Ans[MAXN]; stack<int> sta; inline void solve(int l,int r,int L,int R){ if(l>r) return; if(L<=0) L=1; int mid=l+r>>1,ps=-1; for(register int i=L;i<=R;++i){ if(G[mid].q>=F[i].p&&G[mid].e>=F[i].d){ LL calc=(LL)(G[mid].q-F[i].p)*(LL)(G[mid].e-F[i].d); if(calc>=Ans[mid]){Ans[mid]=calc,ps=i;} } } solve(l,mid-1,L,ps),solve(mid+1,r,ps,R);return; } signed main(){ freopen("Rich.in","r",stdin); freopen("Rich.out","w",stdout); m=read(),n=read(); for(int i=1;i<=m;i++) f[i].p=read(),f[i].d=read(); for(int i=1;i<=n;i++) g[i].q=read(),g[i].e=read(); sort(f+1,f+m+1,cmp1); int l=1; for(register int i=2;i<=m;++i){ if(f[i].p==f[i-1].p) continue; F[++M]=f[l]; l=i; } F[++M]=f[l]; for(register int i=1;i<=M;i++) f[i]=F[i]; m=M; M=0;F[++M]=f[1]; for(register int i=2;i<=m;i++){ if(F[M].d<=f[i].d) continue; F[++M]=f[i]; } sort(g+1,g+n+1,cmp2); l=0; int Minn=INT_MAX; for(register int i=1;i<=n;++i){ while(F[l+1].p<=g[i].q&&l<M) Minn=min(Minn,F[l+1].d),l++; if(Minn>g[i].e&&Minn!=LLONG_MAX) continue; G[++N]=g[i]; } n=N; for(register int i=1;i<=N;++i) g[i]=G[i]; l=1;N=0; for(register int i=2;i<=n;++i){ if(g[i].q==g[i-1].q) continue; G[++N]=g[l]; l=i; } G[++N]=g[l]; n=N; for(register int i=1;i<=N;++i) g[i]=G[i]; for(register int i=1;i<=n;++i){ while(!sta.empty()){ int xx=sta.top(); if(g[xx].e<=g[i].e) sta.pop(); else break; } sta.push(i); } while(!sta.empty()) k[++k[0]]=sta.top(),sta.pop(); N=k[0]; for(register int i=1;i<=N;i++) G[i]=g[k[N-i+1]]; solve(1,N,1,M); LL Maxn=0ll; for(register int i=1;i<=N;i++) Maxn=max(Maxn,Ans[i]); printf("%lld\n",Maxn);return 0; }
菌落
题意简述
共 $T$ 组询问。
有 $n$ 个菌落,每个菌落都有颜色与质量两种属性,最后通过操作将其化为 $1$ 个,问最小代价。
$n\leq 10,T\leq 10$ 。
$solution:$
因为 $n\leq 10$,考虑状压 $dp$ 或者搜索。
状压 $dp$ 可以将 $n$ 个数的并查集祖先记录下来,发现总状态数最多为 $10!$ ,直接转移即可。
时间复杂度 $O(能过)$ 。
搜索直接暴力贡献最小的几个,因为在绝大多数情况下,贪心与最优策略是一样的,所以就过了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<map> #include<vector> #include<climits> #include<cmath> #define int long long #define LL long long using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=11; struct node{ int val,col; }g[MAXN]; int T,n; LL pw[MAXN],fac[MAXN]; int Get(int g[]){ int ans=0; for(int i=2;i<=n;i++) ans+=(g[i]-1)*fac[i-1]; return ans; } int* Get1(int *EE,int Ans){ EE[0]=0; int Fac=fac[n-1]; for(int i=n-1;i>=1;--i){ EE[i+1]=(Ans/Fac)+1; Ans%=Fac; Fac/=i; } EE[1]=1;return EE; } int s[MAXN],tot; LL f[36287800]; vector<int> ve,V; char str[MAXN]; int E[MAXN],S[MAXN],ans0[MAXN],ans1[MAXN]; inline LL W(int s[]){ LL sum=0; for(register int i=1;i<=n;++i){ if(!g[i].col) ans0[s[i]]+=g[i].val; else ans1[s[i]]+=g[i].val; } for(register int i=1;i<=n;++i){ if(s[i]==i) { sum+=(LL)abs(ans1[i]-ans0[i])*(LL)abs(ans1[i]-ans0[i]); ans1[i]=ans0[i]=0; } } return sum; } LL Minn,P,Num,GG,H; int cnt; bool MAP[36287800]; int bug[MAXN]; inline void solve(){ n=read();Minn=LLONG_MAX; for(register int i=1;i<=n;++i){ int val=read();scanf("%s",str+1); g[i].val=val; if(str[1]=='7') g[i].col=0; else g[i].col=1; } tot=0; memset(f,127/3,sizeof(f)); for(register int i=1;i<=n;++i) s[i]=i; Num=Get(s); f[Num]=0;f[Num]=0; ve.push_back(Num); for(register int i=1;i<=n-1;++i){ int siz=ve.size(); for(register int j=0;j<siz;++j){ P=ve[j]; Get1(s,P); E[0]=0; for(register int k=1;k<=n;++k) if(s[k]==k) E[++E[0]]=k; for(register int k1=1;k1<=E[0];++k1){ for(register int k2=k1+1;k2<=E[0];++k2){ for(register int k=1;k<=n;++k){ cnt++; if(s[k]==E[k2]) S[k]=E[k1]; else S[k]=s[k]; } GG=Get(S); if(MAP[GG]==0){MAP[GG]=1;V.push_back(GG);} H=GG; f[H]=min(f[H],f[P]+W(S)); if(i==n-1) Minn=min(Minn,f[H]); } } } ve.clear(); siz=V.size(); for(register int j=0;j<siz;++j) ve.push_back(V[j]); V.clear(); } memset(MAP,0,sizeof(MAP)); printf("%lld\n",Minn);return; } signed main(){ freopen("germ.in","r",stdin); freopen("germ.out","w",stdout); T=read();fac[0]=1ll; for(int i=1;i<=10;i++) fac[i]=fac[i-1]*i; while(T--) solve(); return 0; }
石头
题意简述
$T$ 组询问。
每次求环形最长非严格上升子序列长度,保证数据随机。
$n\leq 10^4,T\leq 10$
$solution:$
对于数据随机必定答案不大,考虑 $dp$ 。
设 $f_{i,j}$ 表示以 $i$ 为结尾,$LIS$ 长度为 $j$ 时的最右左端点。
$f_{i,j}=max{f_{k,j-1}}\space(k<i,a_k\leq a_i)$ ,直接数据结构优化即可。
因为 $j$ 不会很大所以时间复杂度为 $O(答案\times n\log n)$ 。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=20001; int n,a[MAXN],T,f[MAXN][2]; struct Bit{ int maxn[MAXN]; void clear(){memset(maxn,-127/3,sizeof(maxn));return;} int lowbit(int x){return x&-x;} int Query(int x){ int Maxn=INT_MIN; for(int i=x;i;i-=lowbit(i)) Maxn=max(Maxn,maxn[i]); return Maxn; } void modify(int x,int w){ for(int i=x;i<=n;i+=lowbit(i)) maxn[i]=max(maxn[i],w); return; } }bit; void solve(){ n=read(); for(register int i=1;i<=n;++i) a[i+n]=a[i]=read(); for(register int i=1;i<=2*n;++i) f[i][0]=i; int cur=0; for(register int k=2;;++k){ bit.clear(); bool flag=0;cur^=1; for(int i=1;i<=2*n;i++){ f[i][cur]=bit.Query(a[i]),bit.modify(a[i],f[i][cur^1]); if(i-f[i][cur]+1<=n) flag=1; }if(!flag){printf("%d\n",k-1);return;} } } int main(){ freopen("stone.in","r",stdin); freopen("stone.out","w",stdout); T=read(); while(T--) solve(); }
翻转项链
题意简述
一个长度为 $n$ 的序列,你可以翻转一段区间后求环状最大连续子段和,不能非空,求最大值。
$n\leq 10^5$ 。
$solution:$
考虑若翻转区间和最大连续子段和无交集则翻转无用,所以必定有交。
若两端全有交可以等价于无交,所以只有一段有交时有用的。
所以问题变为了求环状最大两段子段和,直接分类讨论 $dp$ 即可。
时间复杂度 $O(n)$ 。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #define int long long using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=200011; int n,Maxn,a[MAXN],Maxn1[MAXN],Maxn2[MAXN],sum,Minn1[MAXN],Minn2[MAXN]; int cnt; signed main(){ freopen("reverse.in","r",stdin); freopen("reverse.out","w",stdout); // freopen("10.in","r",stdin); n=read(); Maxn=-LLONG_MAX; for(int i=1;i<=n;i++){ a[i]=read(),sum+=a[i]; if(a[i]>=0) cnt++; } if(!cnt){ sort(a+1,a+n+1); printf("%lld\n",a[n]); return 0; } if(cnt==1){ sort(a+1,a+n+1); printf("%lld\n",a[n]); return 0; } memset(Maxn1,-127/3,sizeof(Maxn1)),memset(Maxn2,-127/3,sizeof(Maxn2)); memset(Minn1,127/3,sizeof(Minn1)),memset(Minn2,127/3,sizeof(Minn2)); for(int i=1;i<=n;i++) Maxn1[i]=max(Maxn1[i-1]+a[i],a[i]),Minn1[i]=min(Minn1[i-1]+a[i],a[i]); for(int i=1;i<=n;i++) Maxn1[i]=max(Maxn1[i-1],Maxn1[i]),Minn1[i]=min(Minn1[i-1],Minn1[i]); for(int i=n;i>=1;i--) Maxn2[i]=max(Maxn2[i+1]+a[i],a[i]),Minn2[i]=min(Minn2[i+1]+a[i],a[i]); for(int i=n;i>=1;i--) Maxn2[i]=max(Maxn2[i+1],Maxn2[i]),Minn2[i]=min(Minn2[i+1],Minn2[i]); for(int i=1;i<n;i++){ Maxn=max(Maxn,Maxn1[i]+Maxn2[i+1]),Maxn=max(Maxn,sum-Minn1[i]-Minn2[i+1]); } printf("%lld\n",Maxn);return 0; }/* 3 -1 1 -1 */