A题 链接http://acm.hdu.edu.cn/showproblem.php?pid=5645
官方题解:
因为数据规模很小,所以直接用O(n2)时间求出有多少对(i,j)满足ai<aj,然后再除以n(n−1)/2即可。
当然也有O(nlogn)的做法,也很简单。
时间复杂度O(n2)。
我的思考:简单递推,先sort,然后如果a[i]==a[i-1] f[i]=f[i-1] else f[i]=i 然后遍历数组求下和就行
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int main() { int T,n,i; int a[500],f[500]; scanf("%d",&T); while(T--) { scanf("%d",&n); for(i=0;i<n;i++) scanf("%d",&a[i]); if(n==1) { printf("0.000000\n"); continue; } sort(a,a+n); f[0]=0; for(i=1;i<=n-1;i++) { if(a[i]==a[i-1]) f[i]=f[i-1]; else f[i]=i; } double x=0; for(i=0;i<n;i++) { x+=1.0/n*f[i]/(n-1); } printf("%.6lf\n",x); } }
B题 链接http://acm.hdu.edu.cn/showproblem.php?pid=5646
官方题解:
记sum(a,k)=a+(a+1)+⋯+(a+k−1)。
首先,有解的充要条件是sum(1,k)≤n(如果没取到等号的话把最后一个k扩大就能得到合法解)。
然后观察最优解的性质,它一定是一段连续数字,或者两段连续数字中间只间隔1个数。这是因为1≤a<=b−2时有ab<(a+1)(b−1),如果没有满足上述条件的话,我们总可以把最左边那段的最右一个数字作为a,最右边那段的最左一个数字作为b,调整使得乘积更大。
可以发现这个条件能够唯一确定n的划分,只要用除法算出唯一的a使得sum(a,k)≤n<sum(a+1,k)就可以得到首项了。
时间复杂度O(√n),这是暴力把每项乘起来的复杂度。
我的思考:先求出一个数f(x)=x+x+1+……x+k 使f(x)<=sum<=f(x+1)然后将sum-f(x)分配给后sum-f(x)个数,这样求出来的值就可以最大,算是贪心的思想了。
#include<cstdio> #include<cstring> #define LL __int64 using namespace std; const LL MOD=1e9+7; int main() { int T,i; LL ans,sum,a,b,n,k; scanf("%d",&T); while(T--) { scanf("%I64d%I64d",&n,&k); sum=(1+k)*k/2; if(sum>n) { printf("-1\n"); continue; } a=n-sum; b=a/k+1; a%=k; ans=1; for(i=b;i<=b+k-a-1;i++) ans=(LL)ans*i%MOD; for(i=b+k-a+1;i<=b+k;i++) ans=(LL)(ans*i)%MOD; printf("%I64d\n",ans); } return 0; }
官方题解:
对于每个结点i,统计出f[i]表示包含i的连通集有多少个,那么容易看出答案就是所有f[i]的和。
要计算f[i]是经典的树形DP问题。令g[i]表示以i为根的连通集的数目,那么g[i]=∏j(g[j]+1),j是i的儿子,按这个从下往上DP一遍。
然后再用同样的式子从上往下DP一遍就好了。这一步骤写的时候要注意一下不要写错。
时间复杂度O(n)。
我的思考:数状dp,第一次接触,先稍微了解了一下再来看着道题,但还是看了很久
f[i]表示包含以i为根节点的联通块集合中所有元素个数之和
g[i]表示以i为根节点的联通块集合的个数
每当找到i节点的一个新son之后 进行更新
f[i]=f[i]+f[i]*g[son]+g[i]*f[son];
g[i]=g[i]+g[i]*g[son];
#include<cstdio> #include<cstring> #include<vector> #define LL __int64 using namespace std; const int N=200005; const int mod=1e9+7; vector<int>son[N]; LL g[N],f[N]; void dfs(int n,int fa) { for(int i=0;i<son[n].size();i++) { if(son[n][i]==fa) continue; dfs(son[n][i],n); f[n]=f[n]*(g[son[n][i]]+1)%mod+g[n]*f[son[n][i]]%mod; f[n]%=mod; g[n]=g[n]*(g[son[n][i]]+1)%mod; } } int main() { int T,n; scanf("%d",&T); while(T--) { int temp; scanf("%d",&n); for(int i=0;i<=n;i++) { son[i].clear(); g[i]=f[i]=1; } for(int i=2;i<=n;i++) { scanf("%d",&temp); son[temp].push_back(i); son[i].push_back(temp); } LL sum=0; dfs(1,-1); for(int i=1;i<=n;i++) { sum+=f[i]; sum%=mod; } printf("%I64d\n",sum); } return 0; }
官方题解:
记l=log2max{n,m}。
枚举i AND j和i OR j的值分别是多少,因为是有包含关系的,所以枚举量是3l的,用枚举子集的方法。
然后要计算有多少组i,j满足i AND j=a, i OR j=b, 且1≤i≤n,1≤j≤m。i和j肯定都包含了a,剩下b−a的那些bit要分配给i和j,你可以算出最大分配多少才能使i不超过n,j也是同理,于是就确定了一个区间,区间内的都是合法的方案。
所以总的复杂度就是O(l×3l)=O(n1.59logn)
claris老司机提供了一个O(3l)的做法。考虑数位dp,状态是f[i][and][or][2][2],这样的状态数是1+3+⋯+3l=O(3l)的,然后转移也可以O(1)。求gcd也不需要log,可以用一个O(n)预处理支持O(1)询问的结构,见http://mimuw.edu.pl/~kociumaka/files/stacs2013_slides.pdf 。
这题用奇妙的打表或者其他更加厉害的做法也是有可能AC的。
我的理解:
1.有人是打表做的
2.暴力求 a=i|j b=i&j 开一个超大的hash数组记录gcd(a,b) 映射方法为 a*(a+1)/2+b
#include<cstdio> using namespace std; __int16 q[136000000]; int main() { int T,n,m; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); __int64 ans=0; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { int a=i|j; int b=i&j; int tmp=a*(a+1)/2+b; if(q[tmp]!=0) ans+=q[tmp]; else { while(b) { int r=a%b; a=b; b=r; } ans+=a; q[tmp]=a; } } } printf("%I64d\n",ans); } return 0; }
官方题解:
这是一道良心的基础数据结构题。
我们二分a[k]的值,假设当前是mid,然后把大于mid的数字标为1,不大于mid的数字标为0。然后对所有操作做完以后检查一下a[k]位置上是0还是1。
因为只有两种值,所以操作还是不难做的。只要用一个线段树,支持区间求和、区间赋值即可。这样要排序一个区间时只要查询一下里面有几个1和几个0,然后把前半段赋值为0,后半段赋值为1即可(降序的话就是反过来)。
复杂度是O(mlog2n)的。
这题用其他玄学做法或者用更加厉害的平衡树做法也是有可能AC的。
我的思考:如官方题解所说,蒟蒻在线段树下颤栗
#include<cstdio> #include<cstring> using namespace std; const int N=1e5+5; #define lson l, m, rt << 1 #define rson m + 1, r, rt << 1 | 1 int n,pos,Q; int op[N][3]; int S[N<<2],col[N <<2],A[N]; void push_up(int rt) { S[rt]=S[rt<<1]+S[rt<<1|1]; } void push_down(int rt,int m,int len) { if(col[rt]!=-1) { int szr=len>>1,szl=len-szr; col[rt<<1]=col[rt<<1|1]=col[rt]; S[rt<<1]=szl*col[rt];S[rt<<1|1]=szr*col[rt]; col[rt]=-1; } } void build(int x,int l,int r,int rt) { col[rt]=-1; if(l==r) { S[rt]=A[l]<=x?0:1; return; } int m=(l+r)>>1; build(x,lson); build(x,rson); push_up(rt); } int query(int L,int R,int l,int r,int rt) { if(L<=l&&r<=R) return S[rt]; int ret=0,m=(l+r)>>1; push_down(rt,m,r-l+1); if(L<=m) ret+=query(L,R,lson); if(R>m) ret+=query(L,R,rson); return ret; } void update(int L,int R,int x,int l,int r,int rt) { if(L>R) return; if(L<=l&&r<=R) { col[rt]=x; S[rt]=(r-l+1)*x; return; } int m=(l+r)>>1; push_down(rt,m,r-l+1); if(L<=m) update(L,R,x,lson); if(R>m) update(L,R,x,rson); push_up(rt); } bool check(int x) { build(x,1,n,1); for(int i=1;i<=Q;i++) { int sum=query(op[i][1],op[i][2],1,n,1); if(op[i][0]==0) { update(op[i][1],op[i][2]-sum,0,1,n,1); update(op[i][2]-sum+1,op[i][2],1,1,n,1); } else { update(op[i][1],op[i][1]+sum-1,1,1,n,1); update(op[i][1]+sum,op[i][2],0,1,n,1); } } int w=query(pos,pos,1,n,1); return w==0; } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&Q); for(int i=1;i<=n;i++) scanf("%d",&A[i]); for(int i=1;i<=Q;i++) { for(int j=0;j<3;j++) scanf("%d",&op[i][j]); } scanf("%d",&pos); int l=1,r=n,m; while(l<=r) { m=(l+r)>>1; if(check(m)) r=m-1; else l=m+1; } printf("%d\n",r+1); } return 0; }