2020牛客寒假算法基础集训营3

题的质量还是好...据说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 #include 
 2 #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 #include 
 2 #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

2020牛客寒假算法基础集训营3_第1张图片

 

你可以用三对角线构造出2的幂次,然后的哪个位上你需要1,你就可以让这一位联通到n,m,也就是该图中的竖线横线的意思(我是把对角线之下这一格‘R’改成‘B’,向下也联通)。

否则就别把这个二进制幂次加入答案即可,就别动即可。

然后自己构造一下即可。

我的代码还把第31行最右边堵上了,因为(1<<30)已经>1e9+7了,不可能需要这里了。然后就按位联通即可。

 1 #include 
 2 #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 #include 
 2 #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~

你可能感兴趣的:(2020牛客寒假算法基础集训营3)