十连测Day1 题解
A. 奥义商店
有一个商店,n个物品,每个物品有一个价格和一种颜色。
有m个操作,操作有两种,一种是修改一个位置的价格,另一种是购买,每次购买指定一个公差d和一个位置k,找到包含这个位置k公差为d的同色最长等差数列,买下所有物品。让你给这个位置染成t种颜色中的一种(你来指定),其他位置会随机染成t种颜色之一,并保证这n-1个物品中第j种颜色的恰好有c[j]个。求最小期望花费保留四位小数。
注意询问相互独立,询问不会买走物品。
1<=n,m<=10^5,∑t<=2*10^5。
首先我们考虑t=1的情况,这样我们就要找到所有mod d与k同余的数之和。
这是一个经典问题了,把d对于sqrt(n)分类,小于sqrt(n)的修改时直接处理,大于sqrt(n)的查询时直接暴力。
那么我们考虑,要使花的钱最少,你选择的肯定是出现概率最少的颜色,设它有c个。
那么我们考虑一个对答案有贡献的位置k±pd,只有k+d,k+...这p个都是颜色c这个位置才对答案有贡献,即n-1个中指定p个同色的概率。
考虑这样染色的方案数,那么剩下n-1-p个只能有c-p个为这个颜色,方案数就是C(n-1-p,c-p),而概率就是C(n-1-p,c-p)/C(n-1,c)。
设这个概率为f(p),那么f(p)=f(p-1)*(c-p+1)/(n-p)。(自己推啊)
然后最坑的地方到了...注意到t>1时由抽屉原理,c<=(n-1)/2,那么(c-p+1)/(n-p)<=((n-1)/2-p+1)/(n-p)=(0.5n-p+0.5)/(n-p)。
我们随手推一推:(0.5n-p+0.5)/(n-p)<0.5⇔0.5n-p+0.5<0.5n-0.5p⇔p>1。
所以f(p)是呈指数级递减的!那么p大于一定范围的时候我们就可以直接忽略f(p)。
大概取个100显然就够了,于是我们暴力算这个东西,大于100就退出。
#include <iostream> #include <stdio.h> #include <math.h> #include <string.h> #include <time.h> #include <stdlib.h> #include <string> #include <bitset> #include <vector> #include <set> #include <map> #include <queue> #include <algorithm> #include <sstream> #include <stack> #include <iomanip> using namespace std; #define pb push_back #define mp make_pair #define pii pair<int,int> #define ll long long #define ld double #define vi vector<int> #define fi first #define se second #define fe first int n,m,v[233333],cs[233333]; int sum[666][666],S,P=123; ld p[2333]; int main() { freopen("lzz.in","r",stdin); freopen("lzz.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",v+i); S=max(int(sqrt(n)),1); for(int i=1;i<=S;i++) { for(int j=1;j<=n;j++) sum[i][j%i]+=v[j]; } while(m--) { int s; scanf("%d",&s); if(s==1) { int a,b; scanf("%d%d",&a,&b); for(int i=1;i<=S;i++) sum[i][a%i]+=b-v[a]; v[a]=b; continue; } int t,k,d,c=2000000000; scanf("%d%d%d",&t,&k,&d); for(int i=1;i<=t;i++) scanf("%d",cs+i), c=min(c,cs[i]); if(t==1) { if(d<=s) printf("%.4lf\n",(double)sum[d][k%d]); else { ld rp=0; for(int s=k;s<=n;s+=d) rp+=v[s]; for(int s=k-d;s>0;s-=d) rp+=v[s]; printf("%.4lf\n",rp); } continue; } p[0]=1; for(int i=1;i<=P;i++) if(i>c) p[i]=0; else p[i]=p[i-1]*(c-i+1)/(n-i); ld ans=0; for(int s=k,t=0;s<=n&&t<=P;s+=d,++t) ans+=v[s]*p[t]; for(int s=k-d,t=1;s>0&&t<=P;s-=d,++t) ans+=v[s]*p[t]; printf("%.4lf\n",ans); } }
B. 访问计划
一棵n个点有边权的树,现在要从根节点出发遍历每条边并返回根节点,沿路行走的花费为边权,此外可以使用不超过k次传送门,每次花费c元跳到任意一个节点。求最小花费。
1<=n,k<=2000,多组数据,∑n<=10000,答案在int范围内。
首先每条树边只会经过1或2次,这个似乎十分显然,证明可以用欧拉回路的判定来证。
而传送相当于是添加了一些能且仅能经过一次的边,那么每条边走过的奇偶性与该字数内新加顶点奇偶性相同,因为没有传送因为要来回必然是偶数,两个顶点都在子树内的传送对这条边没有意义,有一次传送就会是奇数。
所以(fa[x],x)这条边会经过2-(x子树内新加点个数)%2次。
那么我们只要记f[a][b]为a子树内有b个新加顶点的代价,直接树上背包转移即可。
由jsoi2016那题的证明这样做是O(n^2)的(我不会证啊qaq)
最后显然用f[1][2x]+x*c更新答案即可。
#include <iostream> #include <stdio.h> #include <math.h> #include <string.h> #include <time.h> #include <stdlib.h> #include <string> #include <bitset> #include <vector> #include <set> #include <map> #include <queue> #include <algorithm> #include <sstream> #include <stack> #include <iomanip> using namespace std; #define pb push_back #define mp make_pair #define pii pair<int,int> #define ll long long #define ld double #define vi vector<int> #define fi first #define se second #define fe first #define SZ 666666 int n,k,c,fst[SZ],vb[SZ],vc[SZ],nxt[SZ],M=0; void ad_de(int a,int b,int c) { ++M; nxt[M]=fst[a]; fst[a]=M; vb[M]=b; vc[M]=c; } void gmin(int& x,int y) { if(x>y) x=y; } int siz[SZ],cst[2005][2005],tmp[2005]; void dp(int x,int f) { siz[x]=cst[x][0]=0; for(int e=fst[x];e;e=nxt[e]) { int b=vb[e]; if(b==f) continue; dp(b,x); memset(tmp,127/3,(siz[x]+siz[b]+1)*sizeof(int)); for(int p=0;p<=siz[x];p++) { for(int q=0;q<=siz[b];q++) gmin(tmp[p+q],cst[x][p]+cst[b][q]+vc[e]*(2-(q&1))); } siz[x]+=siz[b]; for(int i=0;i<=siz[x];i++) cst[x][i]=tmp[i]; } cst[x][++siz[x]]=1000000000; for(int i=siz[x];i>=1;i--) gmin(cst[x][i],cst[x][i-1]); } int main() { freopen("mzz.in","r",stdin); freopen("mzz.out","w",stdout); while(scanf("%d%d%d",&n,&k,&c)!=EOF) { M=0; for(int i=1;i<=n;i++) fst[i]=0; for(int i=1;i<n;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); ++a; ++b; ad_de(a,b,c); ad_de(b,a,c); } dp(1,0); int ans=2000000000; for(int i=0;i<=k&&i+i<=n;i++) gmin(ans,cst[1][i+i]+i*c); printf("%d\n",ans); } }
C. 模范学长
给一个n*n的矩阵,矩阵的每个元素为一个多元多项式,问行列式合并同类项后是否每一项系数都是偶数。多项式中的参数为26个字母。
这题好神啊...
首先由Schwartz-Zippel定理(https://en.wikipedia.org/wiki/Schwartz%E2%80%93Zippel_lemma)。
一个域(定义域和值域)为有限域S的d次非零多项式,随机选择每个参数的值带入,把它的值误判为0的概率不超过d/|S|。
那么我们考虑多次随机找一些值带进去,计算行列式,计算它的值是否为0?
那么我们就要找到一个域和一些运算使得合并同类项完系数是偶数就意味着值为0。即满足在此域下x+x=0。
mod 2的域显然太小了误判概率十分高啊。
最神的地方就是:我们模上一个系数模2意义下的多项式!
由于系数在模2意义下是0/1,我们只要用一个二进制数来表示这个多项式就行了。
我们考虑这个域上的加减法,就相当于直接异或。
而一个多项式a模上这个多项式b,只要让a=min(a,a^b)即可。
为什么呢?因为a^b即为b-a,而如果b最高一位a在该位也为1,那么a^b就会小于a,那么我们就实现了取模操作。
那么我们乘法就可以倍增地进行,随机若干次,每次选择一个随机多项式带入26个变量,高斯消元计算行列式即可。
全是套路...
#include <iostream> #include <stdio.h> #include <math.h> #include <string.h> #include <time.h> #include <stdlib.h> #include <string> #include <bitset> #include <vector> #include <set> #include <map> #include <queue> #include <algorithm> #include <sstream> #include <stack> #include <iomanip> using namespace std; #define pb push_back #define mp make_pair typedef pair<int,int> pii; typedef long long ll; typedef double ld; typedef vector<int> vi; #define fi first #define se second #define fe first int xors=647029825; int mul(int a,int b) { int ans=0; while(b) { if(b&1) ans=ans^a; a<<=1; a=min(a,xors^a); b>>=1; } return ans; } #define SZ 666666 int val[SZ]; int gv(char* s) { int ans=0,cur=1; while(*s) { if(*s=='+') ans^=cur, cur=1; else if(isalpha(*s)) cur=mul(cur,val[*s-'a']); else cur=*s-'0'; ++s; } return ans^=cur; } int rndv() { int s=0; for(int i=1;i<=40;i++) s=s*2333+rand()%2333; if(s<0) s=-s; s=min(s,s^xors); return s; } int n,a[103][103]; char str[55][55][103]; bool gauss() { for(int i=1;i<=n;i++) { int p=0; for(int j=i;j<=n;j++) { if(a[j][i]) {p=j; break;} } if(!p) return 1; for(int j=i;j<=n;j++) swap(a[i][j],a[p][j]); int x=a[i][i]; for(int j=i+1;j<=n;j++) { int y=a[j][i]; if(!y) continue; for(int k=i;k<=n;k++) a[j][k]=mul(a[j][k],x)^mul(a[i][k],y); } } return 0; } int main() { freopen("rzz.in","r",stdin); freopen("rzz.out","w",stdout); int T; scanf("%d",&T); while(T--) { scanf("%d",&n); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { scanf("%s",str[i][j]); a[i][j]=gv(str[i][j]); } } bool ok=1; for(int g=1;g<=20;g++) { for(int i=0;i<26;i++) val[i]=rndv(); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) a[i][j]=gv(str[i][j]); } ok=gauss(); if(!ok) break; } if(ok) puts("Yes"); else puts("No"); } }