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

感觉题质量挺好的,能学到东西或者完善技能树。

A了8题,实际上只有7题,有一题队友抬的,dp还是较弱项感觉。

这场感觉数学和思维偏重,开场签了A,B后就停滞了,挂机了一个多小时,还以为要2题结束了。

后来手推反倒是过了当时过的人还不多的E,信心又回来了,状态就上来啦。

顺序是ABEGDFCH,密集过题是1.5~3.5小时过了5题。前10分钟过了两题,(盲目)分析下似乎只要在线2个小时的高效状态就比较不错了。

记录一下思维。赛中没过的贴个代码。

按顺序来吧。

E.做计数

给的式子平方之后,就是要找sqrt(i*j)是整数的组合,记i*j=t 枚举sqrt(t),找t*t的因子即可。

话说我怎么看到这种多少对i,j,k就想到fft了。。。。。最近是不是做fft太多了。想了几分钟fft就排除了。。。

G.判正误

开场10分钟之后看到自闭的题,因为显然取模运算之后的数算出来的答案和原来不一样,也就是令人困惑的点。

但实际上仔细想想,YES的条件十分的苛刻,一旦YES,等式成立,那么这个等式按照任意质数模数算出来都应该同样是成立的。

所以对多个模数快速幂算一下即可,如果都成立,则YES,否则NO。

出题人卡了1e9+7,因为这个实在太常用了,似乎有随便换一个较大的质数就过的代码,实际上很不保险,知道你的模数,可以卡你的。

pow都可以过,有点晕。

D.数三角

看到题,n=500,算了下C(n,3)发现正好2e7左右,于是就开始写暴力,1s能过应该不是问题。。

写了暴力,就判钝角,然后忘了判共线,组不成三角形的情况,WA了一发就过了。

赛后补了n2log的做法,实际上n可以出到更大,建议参考HDU5784。

F.拿物品

交了5发错误的贪心策略,有先拿差值最大,先拿各自值最大等等策略的贪心。

最后冷静下来玩了临界情况的样例,当对方能得到很大的值,你是阻止对方拿?还是管自己拿大?然后就出来了,是拿a+b最大。

题解给的实际上非常有说服力。建议参考题解的证明。

C.算概率

开场自闭题2,最原始想法是组合数枚举嘛,就是你n个题做对k个,有C(n,k)的选法,但这样肯定做不了。

灵光乍现,或者参考群友提到的概率dp(虽然我没做过概率dp...)

想到dp,然后你就会了。

dp[i][j]记录 i 道题做对 j 道的概率。

然后转移很自然,就是  dp[i-1][j]*这道题做错 + dp[i-1][j-1]*这道题做对的概率。

二维dp的思路比较清晰。

然后实际上可以优化掉第二维。。。就是因为每次只与前一次有关,第二重循环反向枚举,滚动掉第二维?背包?(学了下,但还是不太会)  但复杂度n^2跑不了,数组开二维问题也不大。

(这就体现了dp弱项了....)

H.施魔法

就是排完序后 找 每段长度>=k的不重叠子段  使得总cost最小,cost为两个端点的差值。

队友抬的题,这题我比赛里也只会n^2的dp,

比赛时交了一发TLE的n^2的dp...实际上了就可以从式子上优化到O(n).

我是这么写的

        for(int i=k;i<=n;++i)

         {
             for ( int j=0;j<=i-k;++j)
             {
                 dp[i]=min(dp[i],dp[j]+a[i]-a[j+1]);
             }
 
         }

 其实是跟题解里的式子一样的,第一个等号出来了,关键在于第二个等号,我们发现对于第二个循环枚举j来说,a[i]是多余的,可以提出来,然后剩下dp[j]-a[j+1]。

其实我们枚举第二个循环的目的就找这个最小的dp[j]-a[j+1],但这个式子只与当前的自变量有关,我们可以直接用一个pre变量在算的同时维护掉,省去这个第二重枚举。

就是dp递推转移式子的化简来实现dp复杂度的优化。

赛后给的题解里还是能学到挺多的感觉。

 1 #include <bits/stdc++.h>
 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=3e5+7;
10 const int INF=0x3f3f3f3f;
11 const int MOD=1e9+7;
12  
13 int dp[MAXN];
14 int a[MAXN];
15  
16 int main()
17 {
18     ios::sync_with_stdio(false);
19     cin.tie(0);
20     int n,k;
21     cin>>n>>k;
22     for(int i=1;i<=n;++i) cin>>a[i];
23     if(k==1) cout<<0<<endl;
24     else
25     {
26         memset(dp,0x3f,sizeof(dp));
27         sort(a+1,a+n+1);
28         int pre=-a[1];
29         for(int i=k;i<=n;++i)
30         {
31             dp[i]=pre+a[i];
32             pre=min(pre,dp[i-k+1]-a[i-k+2]);
33         }
34         cout<<dp[n]<<endl;
35     }
36     return 0;
37 }

I.建通道

比赛里感觉思路大致方向对了,但没想好连边方式。导致想不好...

就是求个特殊位运算的最小生成树。

显然相同的v值之间无需建边,cost=0,就排序去重一下即可。剩下的点m个;

我们还需要在剩下m个点连m-1条边。注意此处的特判,别判n-1,而是去重后的点数-1。

然后找到最小的二进制位有0,有1,答案就是(m-1)*这位权值,为啥,因为所有的当前位这位二进制为1的点都可以和为0的这个点连,反之亦然。然后写了个O(30n)的写法。

出题人的写法统计二进位01存在性统计 用 & 和 | 扫一遍就更巧妙啦。学到了

 1 #include <bits/stdc++.h>
 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 v[MAXN];
14  
15 int main()
16 {
17     ios::sync_with_stdio(false);
18     cin.tie(0);
19     int n;
20     cin>>n;
21     for(int i=0;i<n;++i)cin>>v[i];
22     sort(v,v+n);
23     ll m=unique(v,v+n)-v;
24     if(m==1) cout<<0<<endl;
25     else
26     {
27  
28         for(int i=0;i<=30;++i)
29         {
30             int cnt0=0,cnt1=0;
31             for(int j=0;j<m;++j)
32             {
33                 if((v[j]&(1ll<<i))==0) cnt0++;
34                 else cnt1++;
35             }
36             if(cnt0&&cnt1)
37             {
38                 cout<<1ll*(1ll<<i)*(m-1)<<endl;
39                 break;
40             }
41         }
42     }
43     return 0;
44 }

J.求函数

又是道可以学习的题。这种题之前还没遇到过。代入一下发现,就是    (((k(k(k(k*1+b)+b)+b)+b))) 这样的嵌套式子,拆开来就是题解给的那个式子。

 然后明确这个式子的区间合并方式,(这题的灵魂)就过了。

左边那个显然直接乘就好。

右边那个,.....好吧我讲不清,建议看题解,反正数学式子感觉到了就行...(逃~

https://ac.nowcoder.com/discuss/364961?type=101&order=0&pos=6&page=3

然后线段树分别维护这两个值就行,区间合并函数写好,或者重载一个运算符就行。

 1 #include <bits/stdc++.h>
 2 #ifndef ONLINE_JUDGE
 3 #define debug(x) cout << #x << ": " << x << endl
 4 #else
 5 #define debug(x)
 6 #endif
 7 #define lson (o<<1)
 8 #define rson (o<<1|1)
 9 using namespace std;
10 typedef long long ll;
11 const int maxn=2e5+7;
12 const int INF=0x3f3f3f3f;
13 const int MOD=1e9+7;
14 
15 struct node
16 {
17     ll k,b;
18     node(ll x=0,ll y=0):k(x),b(y){}
19     friend node operator + (const node &x,const node &y)
20     {
21         return {x.k*y.k%MOD,(x.b*y.k+y.b)%MOD};
22     }
23 }t[maxn<<2];
24 ll k[maxn],b[maxn];
25 void build(int o,int l,int r)
26 {
27     if(l==r)
28     {
29         t[o]={k[l],b[l]};
30         return ;
31     }
32     int mid=l+r>>1;
33     build(lson,l,mid);
34     build(rson,mid+1,r);
35     t[o]=t[lson]+t[rson];
36 }
37 
38 void update(int o,int l,int r,int pos,int k,int b)
39 {
40     if(l==r)
41     {
42         t[o]={k,b};
43         return ;
44     }
45     int mid=l+r>>1;
46     if(pos<=mid) update(lson,l,mid,pos,k,b);
47     else update(rson,mid+1,r,pos,k,b);
48     t[o]=t[lson]+t[rson];
49 }
50 node query(int o,int l,int r,int ql,int qr)
51 {
52     if(ql<=l && qr>=r) return t[o];
53     int mid=l+r>>1;
54     if(qr<=mid) return query(lson,l,mid,ql,qr);
55     if(ql>mid) return query(rson,mid+1,r,ql,qr);
56     return query(lson,l,mid,ql,qr)+query(rson,mid+1,r,ql,qr);
57 }
58 
59 int main()
60 {
61     int n,m;
62     scanf("%d%d",&n,&m);
63     for(int i=1;i<=n;++i) scanf("%lld",&k[i]);
64     for(int i=1;i<=n;++i) scanf("%lld",&b[i]);
65     build(1,1,n);
66     for(int i=0,op,l,r;i<m;++i)
67     {
68         scanf("%d",&op);
69         if(op==1)
70         {
71             int pos,x,y;
72             scanf("%d%d%d",&pos,&x,&y);
73             update(1,1,n,pos,x,y);
74         }
75         else
76         {
77             scanf("%d%d",&l,&r);
78             node res=query(1,1,n,l,r);
79             printf("%lld\n",(res.k+res.b)%MOD);
80         }
81     }
82     return 0;
83 }

结束啦,逃...

猜你喜欢

转载自www.cnblogs.com/Zzqf/p/12273756.html