题的质量还是好...据说sjf出的题?
不过我还是A了7题......感觉自己水平还挺稳定的??? 7 8 7?实际上是7 7 7 冥冥中自有7意。
然后这场前期状态挺好的,一路不怎么卡,1小时5题2小时6题,都是1A,切过去挺舒服的嘻嘻。。。不过,之后就没了...
后来想pokemon去了,一直在想怎么降n^2复杂度,(200n) 我都想到,居然没想到t差200就必能这个关键点。。。
最后看G过的多,莽了1小时树状数组算贡献终于过了...就没啦
果然只能2小时high-level吗 ~~~
记录一下思维和补题。
A.牛牛的DRB迷宫I dp递推一下,根据当前位置的指令,他的下一个可达位都可以加上这一位的方案。话说这个他逆向题 B的idea真的好nb啊。
H.牛牛的k合因子数
比赛里我是无脑写的,欧筛一遍求质数,然后处理范围内每个数的非质数因子,即(因子总数-质数因子个数-1),然后统计一下的。。。大概 n·sqrt(n)的复杂度吧。
然后其实赛后看题解,发现这个求k因子的过程可以通过埃式筛Eratosthenes的过程直接求出来,因为埃筛是对每个数,将他的倍数都处理一遍,那么对一个非质数的倍数,那他的非质数因子都可以+1,完美符合我们的期待。比上面法好不少,而且是出题人的本意应该。
F.牛牛的Link Power I
比赛里我是统计每个位置前缀的数能产生的贡献,写的有点烦了感觉。实际上三个变量就可以搞定。
C.牛牛的数组越位 D.牛牛与二叉树的数组存储
按题意模拟即可。酷酷酷
I.牛牛的汉诺塔
我找规律做的,这种题一般都是照着他给伪代码打表,然后测小数据,找大数据递推的规律即可。发现好像根据奇偶不同每次递增有三个变量不变。然后总数分配减一下即可。
G.牛牛的Link Power II
我树状数组(线段树差不多应该)维护贡献做的,开两个树状数组,分别表示每个位置的信息(其实就是这个位置的下标),每个位置的1(是否有1).
然后你要支持修改,和动态维护ans。对每个变更的位置,产生的影响只有这个位置的1带来的贡献,没错吧?
那对于将0改成1的pos,增加的贡献是多少?肯定是 前面的1产生的贡献 + 后面的1产生的贡献。将1改成0的位置也一模一样的,减去即可。
那他们分别是多少呢?
我们记前面的1的个数为l ,后面的1显然是总数-l,记作r = cnt1-l;
然后我们记录的位置信息就有用了,其实为啥要记录位置,还要求他们的前缀和?这是因为我们需要每个1和他们前面和后面1之间的相对信息,他们的差就是他们之间产生的贡献。
前面的1产生的贡献是 l*pos - pos前缀的sum
后面的1产生的贡献是 pos后缀(不包括pos本身)的sum-r*pos
大致推出上面两个式子,这题就结束啦。
然后比赛里也观(xia)察(gao)了好久。感觉对了,式子就推出来了。还是靠玄学?所以我才要总结一下啊......
1 #include2 #ifndef ONLINE_JUDGE 3 #define debug(x) cout << #x << ": " << x << endl 4 #else 5 #define debug(x) 6 #endif 7 using namespace std; 8 typedef long long ll; 9 const int maxn=2e5+7; 10 const int INF=0x3f3f3f3f; 11 const int mod=1e9+7; 12 13 struct BIT 14 { 15 int sum[maxn]; 16 void clear(){memset(sum,0,sizeof sum);} 17 int lowbit(int x){return x & (-x);} 18 void add(int x,int val) 19 { 20 while (x < maxn) 21 { 22 sum[x] += val; 23 sum[x]%=mod; 24 x += lowbit(x); 25 } 26 } 27 ll query(int x) 28 { 29 if (x <= 0) return 0; 30 ll res = 0; 31 while (x) 32 { 33 res += sum[x]; 34 res%=mod; 35 x -= lowbit(x); 36 } 37 return res; 38 } 39 ll query(int l,int r){return query(r) - query(l -1);} 40 }bit,bit2; 41 42 char s[maxn]; 43 44 int main() 45 { 46 ios::sync_with_stdio(false); 47 cin.tie(0); 48 int n; 49 cin>>n>>s+1; 50 ll sum=0,cnt1=0,ans=0; 51 for(int i=1;i<=n;++i) 52 { 53 sum+=cnt1; 54 sum%=mod; 55 if(s[i]=='1') 56 { 57 ans+=sum; 58 ans%=mod; 59 cnt1++; 60 bit.add(i,i); 61 bit2.add(i,1); 62 } 63 } 64 cout< endl; 65 int q; 66 cin>>q; 67 while(q--) 68 { 69 int op,pos; 70 cin>>op>>pos; 71 int l=bit2.query(pos); 72 int r=cnt1-l; 73 if(op==1) 74 { 75 ans+=((1ll*l*pos)%mod-bit.query(pos) +mod)%mod; 76 ans=(ans+mod)%mod; 77 ans+=(bit.query(pos+1,n)-(1ll*r*pos)%mod+mod) % mod; 78 ans=(ans+mod)%mod; 79 bit.add(pos,pos); 80 bit2.add(pos,1); 81 cnt1++; 82 } 83 else 84 { 85 ans-=((1ll*l*pos)%mod-bit.query(pos) +mod)%mod; 86 ans=(ans+mod)%mod; 87 ans-=(bit.query(pos+1,n)-(1ll*r*pos)%mod+mod)%mod; 88 ans=(ans+mod)%mod; 89 bit.add(pos,-pos); 90 bit2.add(pos,-1); 91 cnt1--; 92 } 93 cout< '\n'; 94 } 95 return 0; 96 }
J.牛牛的宝可梦Go
出题人真的已经疯狂在提示了,就是不会出现t一样的。
然后我也想到了floyd处理完最短路,对宝可梦的时间排序后,只要两个宝可梦之间的时间差不小于他们位置最短的距离,答案就可以变的更大。
然后我就一直在和n^2斗智斗勇,试图降低复杂度。
我也注意到了n=200,图最多200个点,很少,唉,那我们对每个宝可梦,处理200个点咋样?你还是要每个点的最大的且t和pos都满足条件的值呀...想歪啦。
然后正解是因为每个t不一样,n最多200,那么只要时间间隔超过n,这两点之间必能够在这段时间中跑到,所以你最多只需要遍历当前之前200个宝可梦,是否满足条件。
对于更之前的宝可梦,他们之间的t之差必然>=n,就必然可达了。
所以就有了200n的做法,对于更之前的,只需要记录一个最大值即可。
但这里要注意,似乎会存在不可达的点,dp要初始为-INF,为0的话,对于起点不可达的点,也进行扩展,是不可取的。而dp[0]=0,从这里扩展是可以的。
这里我估计我自己写得WA穿都想不到。原图没保证一定联通。
1 #include2 #ifndef ONLINE_JUDGE 3 #define debug(x) cout << #x << ": " << x << endl 4 #else 5 #define debug(x) 6 #endif 7 using namespace std; 8 typedef long long ll; 9 const int maxn=2e5+7; 10 const int INF=0x3f3f3f3f; 11 const int MOD=1e9+7; 12 13 int mp[210][210]; 14 ll dp[maxn]; 15 16 struct node 17 { 18 int p; 19 ll t,val; 20 }a[maxn]; 21 22 bool cmp(node a,node b){return a.t<b.t;} 23 24 int main() 25 { 26 ios::sync_with_stdio(false); 27 cin.tie(0); 28 int n,m; 29 cin>>n>>m; 30 memset(mp,0x3f,sizeof(mp)); 31 while(m--) 32 { 33 int u,v; 34 cin>>u>>v; 35 mp[u][v]=mp[v][u]=1; 36 } 37 for(int i=1;i<=n;++i) mp[i][i]=0; 38 for(int k=1;k<=n;++k) 39 { 40 for(int i=1;i<=n;++i) 41 { 42 for(int j=1;j<=n;++j) 43 mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]); 44 } 45 } 46 int k; 47 cin>>k; 48 for(int i=1;i<=k;++i) 49 cin>>a[i].t>>a[i].p>>a[i].val; 50 sort(a+1,a+1+k,cmp); 51 a[0].t=0; 52 a[0].p=1; 53 ll ans=0; 54 ll premax=0; 55 for(int i=1;i<=k;++i) 56 { 57 if(i>200) 58 { 59 premax=max(premax,dp[i-200]); 60 dp[i]=a[i].val+premax; 61 } 62 else 63 dp[i]=-INF; 64 for(int j=1;j<=200 && i>=j;++j) 65 { 66 if(mp[a[i].p][a[i-j].p]<=a[i].t-a[i-j].t) 67 dp[i]=max(dp[i],dp[i-j]+a[i].val); 68 } 69 ans=max(ans,dp[i]); 70 } 71 cout< endl; 72 return 0; 73 }
B.牛牛的DRB迷宫II
好有趣的构造题啊啊啊。
构造一个二进制码生成器,辣是zdnb。
就是你发现(实际上不能发现)
图转载自题解:https://ac.nowcoder.com/discuss/365306?type=101&order=0&pos=8&page=2
你可以用三对角线构造出2的幂次,然后的哪个位上你需要1,你就可以让这一位联通到n,m,也就是该图中的竖线横线的意思(我是把对角线之下这一格‘R’改成‘B’,向下也联通)。
否则就别把这个二进制幂次加入答案即可,就别动即可。
然后自己构造一下即可。
我的代码还把第31行最右边堵上了,因为(1<<30)已经>1e9+7了,不可能需要这里了。然后就按位联通即可。
1 #include2 #ifndef ONLINE_JUDGE 3 #define debug(x) cout << #x << ": " << x << endl 4 #else 5 #define debug(x) 6 #endif 7 using namespace std; 8 typedef long long ll; 9 const int MAXN=2e5+7; 10 const int INF=0x3f3f3f3f; 11 const int MOD=1e9+7; 12 13 char mp[55][55]; 14 ll dp[100][100]; 15 16 int main() 17 { 18 ios::sync_with_stdio(false); 19 cin.tie(0); 20 int n=32,m=32; 21 for(int i=1;i<=n;++i) 22 { 23 for(int j=1;j<=m;++j) 24 { 25 if(i==n || i==n-1 && j>=i) mp[i][j]='R'; 26 else if(i==j) mp[i][j]='B'; 27 else if(i==j+1) mp[i][j]='R'; 28 else mp[i][j]='D'; 29 } 30 } 31 int k; 32 cin>>k; 33 for(int i=1;i<32;++i) 34 { 35 if(k&(1ll<<(i-1))) 36 mp[i+1][i]='B'; 37 } 38 cout< ' '< endl; 39 for(int i=1;i<=n;++i) 40 cout< 1<<endl; 41 return 0; 42 }
E.牛牛的随机数
数位dp的解法没学。学的按位统计的做法。
这也是道 按二进制位算贡献的题。暴力算不了啊,由于异或运算的特殊性,可以快速统计。好吧以后看见位运算大数据的题,一定要想这种运算特别的性质。然后尝试按位统计???
原因在于异或的答案一次贡献最多只有1,每个区间的cnt0 * 另一个区间cnt1 * 这位的权值,再交换一下两个区间算一遍这位就好了。
观察知道二进制每位的01有循环节,就可以很快的算0~x,有多少个这位上的1,这位上的0。(感觉这个已经很nb了)
我忽然想到好像只需要一个函数,总-多少个1,不就是0嘛。不过也差不多。
然后这里学的题解 容斥写法,不过好像可直接统计 r1的区间-(l1-1)的区间的1和0数就对了,也差不多。
1 #include2 #ifndef ONLINE_JUDGE 3 #define debug(x) cout << #x << ": " << x << endl 4 #else 5 #define debug(x) 6 #endif 7 using namespace std; 8 typedef long long ll; 9 const int maxn=2e5+7; 10 const int inf=0x3f3f3f3f; 11 const int mod=1e9+7; 12 13 ll getsum0(ll x,int bit) 14 { 15 x++; 16 ll ans=(x/(2ll< mod; 17 if((x/(1ll< 1) ans+=(1ll<<bit); 18 else ans+=x%(1ll<<bit); 19 return ans%mod; 20 } 21 22 ll getsum1(ll x,int bit) 23 { 24 x++; 25 ll ans=(x/(2ll< mod; 26 ans+=((x/(1ll< 1)*(x%(1ll<<bit)); 27 return ans%mod; 28 } 29 30 ll getval(ll lim1,ll lim2) 31 { 32 ll ans=0; 33 for(int i=0;i<=60;++i) 34 { 35 ll tmp=(1ll<mod; 36 ans+=getsum0(lim1,i)*getsum1(lim2,i)%mod*tmp%mod+getsum0(lim2,i)*getsum1(lim1,i)%mod*tmp%mod; 37 ans%=mod; 38 } 39 return ans; 40 } 41 42 ll quick(ll x,ll n) //快速幂 x^n 43 { 44 ll res=1; 45 x%=mod; 46 if(x==0) return 0; //减少特判防止倍数次 47 while(n) 48 { 49 if(n&1) res=(res*x)%mod; 50 x=(x*x)%mod; 51 n>>=1; 52 } 53 return res; 54 } 55 ll inv(ll a) //逆元 费马小定理,要求 a,mod互素 56 { 57 return quick(a,mod-2); 58 } 59 int main() 60 { 61 ios::sync_with_stdio(false); 62 cin.tie(0); 63 int t; 64 cin>>t; 65 while(t--) 66 { 67 ll l1,r1,l2,r2; 68 cin>>l1>>r1>>l2>>r2; 69 ll ans=0; 70 ans+=getval(r1,r2); 71 ans-=getval(l1-1,r2); 72 ans-=getval(l2-1,r1); 73 ans+=getval(l1-1,l2-1); 74 ans=(ans%mod+mod)%mod; 75 ans=ans*inv(((r1-l1+1)%mod)*((r2-l2+1)%mod))%mod; 76 cout< endl; 77 } 78 return 0; 79 }
终于写(水)完了,逃去LOL~