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

https://ac.nowcoder.com/acm/contest/3007#question

 

前两天打的最后一场,过的题最少的一场......正好碰了盲区加状态差,也问题不大。

 

md,刚刚在门口吃东西撞了玻璃门,镜框坏了,被迫出门上街换了镜框。。。。。我真傻,真的。撞玻璃门是真实存在的。

 

然后补题记录思维。

 

A.配对  好像是个啥不等式,以前上课老师讲过,感觉差不多。排序后小的和大的组合一下即可。

G.号序列 跟前一天的差不多,模拟配对即可。

J.签到题 冷静推式子...话说我也推了半个小时???真的一点也不冷静其实我。

 

---------------然后就陷入自闭-----------

 

F.十字阵列 这个F第一次看样例根本看不懂,后来才知道要求的是wij*(i+j) 。加个乘号真的更清晰好吗。不然   光一个 Σwij(i+j) 感觉真的8太行

反正我有问题,看了好久加好几次通知才看出来。看懂了就算一下贡献就过了。

 

D.重排列 

开始只知道求A的重排列,那对A排序肯定不影响,没发现A的任意排列,也可以将B对应A的排列给找到,所以对B排序也不会影响。

然后对A对B都排个序,然后找每位 从A中能填多少种数,就是相应的B里二分查找一下有多少个A中的数小于等于B当前这个数,用upper_bound找一下,同时减去已经被前位占的数i,即可。

 

B.图 

完全没听说过的基环树,基环内向树,处理环的经验相对欠缺,做不来。比赛的时候并未深入思考。看了代码发现也还行叭。就遍历过程中dfs时候优先处理环的答案,先把环的答案全处理出来,由于内向树,出度为1,用while就可以了。先把内部搞定,然后外部就是逐层加1了。然后标程处理环的时候模拟了栈,感觉还挺麻烦的。

参考别人的这份代码还不错,就理解下dfs到环,区分已经vis,ans到的答案怎么更新即可,遇到环就已经把这个环的答案全部记录上。

 

 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=1e6+7;
10 const int inf=0x3f3f3f3f;
11 const int mod=1e9+7;
12  
13 int vis[maxn];
14 int to[maxn];
15 int ans[maxn];
16 int res;
17  
18 void dfs(int x)
19 {
20     vis[x]=1;
21     if(vis[to[x]])
22     {
23         if(!ans[to[x]])
24         {
25             int now=x,num=1;
26             while(to[now]!=x)
27             {
28                 now=to[now];
29                 num++;
30             }
31             now=x;
32             ans[x]=num;
33             while(to[now]!=x)
34             {
35                 now=to[now];
36                 ans[now]=num;
37             }
38             res=max(res,num);
39         }
40         else
41             ans[x]=ans[to[x]]+1;
42     }
43     else
44     {
45         dfs(to[x]);
46         if(!ans[x]) ans[x]=ans[to[x]]+1;
47     }
48     res=max(res,ans[x]);
49 }
50  
51 int main()
52 {
53     ios::sync_with_stdio(false);
54     cin.tie(0);
55     int n;
56     cin>>n;
57     for(int i=1,x;i<=n;++i)
58     {
59         cin>>x;
60         to[i]=x;
61     }
62     for(int i=1;i<=n;++i)
63     {
64         if(!vis[i]) dfs(i);
65     }
66     cout<endl;
67     return 0;
68 }

 

基环树优先考虑处理环。

 

C.汉诺塔 

Dilworth定理比赛里我是真的想到了啥啥定理来着,前几天CF好像碰到过,想到了就是求最小升序划分就最长降序子序列(反正两个形容词都反一下就对了)。

然后我不会nlog的子序列求法,LIS没学好,其实是大概有印象的,维护一下新数组,二分去替换更新最优的答案。这题我是把x按从大到小排序的,然后就是求y同样降序的最小划分,也就是求y的最长上升子序列了。这样排序处理可以自己设计一下,更方便。

然后就是LIS的事情了,每次二分找,如果在新数组中找不到比当前这个y大的,(比当前大说明可以替换,就是扩充这一组,把大的换成更小的,保证了x更小,y更小),找不到,就新增一组,同时组号就是在新数组中的下标。

由于这里保证没有相同的x和y,所以upper_bound和lower一样可以。

 

#include 
#ifndef ONLINE_JUDGE
#define debug(x) cout << #x << ": " << x << endl
#else
#define debug(x)
#endif
using namespace std;
typedef long long ll;
const int maxn=2e5+7;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
 
struct node
{
    int x,y,id;
}a[maxn];
bool cmp(node a,node b)
{
    return a.x>b.x;
}
int n,cnt,st[maxn],ans[maxn];
 
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)
    {
        cin>>a[i].x>>a[i].y;
        a[i].id=i;
    }
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;++i)
    {
        int pos=upper_bound(st+1,st+1+cnt,a[i].y)-st;
        st[pos]=a[i].y;
        if(pos>cnt) cnt=pos;
        ans[a[i].id]=pos;
    }
    cout<endl;
    for(int i=1;i<=n;++i)
        cout<' ';
    cout<<endl;
    return 

 

 

 

E.立方数 

这题也挺搞,看了题面,又看到群里有人说Pollard_rho 然后我刚好接触过,(仅仅接触过,没完全懂)然后疯狂找板子改,然后疯狂T,其实这个大数质因子分解的复杂度是O(N^1/4)据说,然后T*(N^1/4)高达3e8过不去也正常,况且分解完map统计可能又会变大?

题解给的做法又是要脑子的。因为要找最大的a,使得a^3*b=n嘛。a最大也就1e6啊,显然可以筛1e6内素数,然后就有T*(n^1/3)/(ln(n^1/3))  后面那个为素数密度。。。反正还是很大。

然后脑子转下?就有了筛n^1/4内素数,n^1/4内素数全部筛掉并统计三次方进答案,最后剩下的数要么是n^1/4以上数的三次方,要么对答案无贡献为啥呢?

假设如果剩下的数是(n^1/4以上的质数)^3 *b(b>1),那么b要么应该在前面被筛掉,要么应该大于n^1/4,乘起来反而大于n,显然矛盾了。

然后就证明完了,那么完了,脑子该去哪找?

所以记录下思维吧,其实先常规思考,最后尝试往更小范围的质数想,降低复杂度。

最后也可以二分,只是check嘛,方法很多。

 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=31700;
10 const int inf=0x3f3f3f3f;
11 const int mod=1e9+7;
12 
13 int vis[maxn];
14 vector<int>pri;
15 vector ptri;
16 int main()
17 {
18     for(int i=2;ii)
19     {
20         if(!vis[i])
21         {
22             pri.push_back(i);
23             ptri.push_back(1ll*i*i*i);
24             for(int j=i*i;ji)
25                 vis[j]=1;
26         }
27     }
28     int t;
29     scanf("%d",&t);
30     while(t--)
31     {
32         ll n;
33         scanf("%lld",&n);
34         int ans=1;
35         for(int i=0;ii)
36         {
37             if(n%pri[i]==0)
38             {
39                 while(n%ptri[i]==0)
40                 {
41                     ans*=pri[i];
42                     n/=ptri[i];
43                 }
44                 while(n%pri[i]==0)
45                     n/=pri[i];
46             }
47         }
48         int t=pow(n,1.0/3);
49         for(int i=max(1,t);i<=t+1;++i)
50             if(1ll*i*i*i==n) {ans*=i;break;}
51         printf("%d\n",ans);
52     }
53     return 0;
54 }

 

I.导航系统

n=500,可为啥好多n^3代码都能过呢,1s能跑1e8多吗?我迷惑啦。n^3确实好理解。

就是对每个点,肯定至少有一条和其他点直接相连的边,然后考虑最小生成树的过程,这个直接相连的边肯定是这两个点之间的最短距离没毛病,通过最小生成树找到这n-1条边。

然后通过n-1条边还原出原图的最短距离,再和原图比较,这是我看了别人代码和题解的理解。然后我也n^3 floyd了,毕竟好理解,也能过。。。

 

 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=3e5+7;
10 const int inf=0x3f3f3f3f;
11 const int mod=1e9+7;
12  
13 ll mp[505][505];
14 ll d[505][505];
15 int ans[505],fa[505];
16 struct edge
17 {
18     int u,v,w;
19 }e[maxn];
20 int tot;
21 void addedge(int u,int v,int w)
22 {
23     e[tot].u=u;
24     e[tot].v=v;
25     e[tot++].w=w;
26 }
27 bool cmp(edge a,edge b) {return a.w<b.w;}
28  
29 int find(int x)
30 {
31     if(fa[x]==-1) return x;
32     return fa[x]=find(fa[x]);
33 }
34  
35 int main()
36 {
37     ios::sync_with_stdio(false);
38     cin.tie(0);
39     int n;
40     cin>>n;
41     for(int i=1;i<=n;++i)
42     {
43         for(int j=1;j<=n;++j)
44         {
45             cin>>mp[i][j];
46             if(i!=j) addedge(i,j,mp[i][j]);
47         }
48     }
49     sort(e,e+tot,cmp);
50     memset(fa,-1,sizeof(fa));
51     for(int i=1;i<=n;++i)
52         for(int j=1;j<=n;++j)
53             if(i==j) d[i][j]=0;
54             else d[i][j]=1e15;
55     int cnt=0;
56     for(int i=0;ii)
57     {
58         int u=e[i].u,v=e[i].v,w=e[i].w;
59         int t1=find(u),t2=find(v);
60         if(t1!=t2)
61         {
62             fa[t1]=t2;
63             ans[cnt++]=w;
64             d[u][v]=d[v][u]=w;
65         }
66     }
67     for(int k=1;k<=n;++k)
68         for(int i=1;i<=n;++i)
69             for(int j=1;j<=n;++j)
70                 d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
71     for(int i=1;i<=n;++i)
72         for(int j=1;j<=n;++j)
73             if(d[i][j]!=mp[i][j])
74                 {cout<<"No\n";return 0;}
75     cout<<"Yes\n";
76     for(int i=0;i'\n';
77     return 0;
78 }

 

H.云

神奇的扫描线。观察发现(发现不了) 处理相对运动好吧。。。又是物理。。。题解讲的还行,看代码的时候主要不知道投影坐标怎么算的。

然后发现列个式子就出来了,一个y=x+q-p,与y=-x联立,就可以解得,然后等比例放大并且横一下就可以,直接简化为  纵-横。

同时每个矩形投影为2个点,有左有右,左是标记开始统计这个,右是结束这个的统计。方便设为1,和-1,两个象限分别种类为0和1.

然后排序扫描线,对于每个是进入的矩形左端点,加上   在他之前进入并且没有出去的另一种   就是答案,保证没有重复,因为 我统计的是之前进入的另一个种类 对当前的影响,非常的神奇,扫描一遍就出来了。然后有个细节就是位置相同的,左端点先进入,种类倒无所谓,排序的时候注意处理一下即可。

 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=4e5+7;
10 const int inf=0x3f3f3f3f;
11 const int mod=1e9+7;
12 
13 struct node
14 {
15     int pos,lr,type;
16 }e[maxn];
17 
18 bool cmp(node a,node b)
19 {
20     return a.posb.lr;
21 }
22 ll cnt[2];
23 
24 int main()
25 {
26     int n,m,x,y,p,q,k=0;
27     scanf("%d%d",&n,&m);
28     for(int i=0;ii)
29     {
30         scanf("%d%d%d%d",&x,&y,&p,&q);
31         e[k++]={q-p,1,0};
32         e[k++]={y-x,-1,0};
33     }
34     for(int i=0;ii)
35     {
36         scanf("%d%d%d%d",&x,&y,&p,&q);
37         e[k++]={q-p,1,1};
38         e[k++]={y-x,-1,1};
39     }
40     sort(e,e+k,cmp);
41     ll ans=0;
42     for(int i=0;ii)
43     {
44         cnt[e[i].type]+=e[i].lr;
45         if(e[i].lr==1)
46             ans+=cnt[e[i].type^1];
47     }
48     printf("%lld\n",ans);
49     return 0;
50 }

 

 

然后这6套题差不多补完了,补了58道吧?除了5的大模拟和4的没懂的计数。至少要么比赛里出了,要么赛后看题解看好多份别人代码 加 自己写一遍甚至二种写法练下了。

补的还算细吧,希望能记住啦啦啦啦啦 。逃~

 

哪里不足或者迷惑,评论区可以交流。等一手互动。

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