暑期集训第五天(6-26)题解及总结

先来大概说一下今天的考试,今天是集训以来第一次NOI赛制的训练,由于四道有两道原题,所以也是我集训以来考的最好的一次,如果不算因为‘\r’引起的那一百分的失分的话,进前五应该是没有问题的,但是毕竟是错了,这是一个值得反思的地方,WIndows的换行不只有'\n',还有'\r',所以以后如果我想要输入一个字符的话,尽量还是用scanf,还有在现阶段快读并不是那么必要(lin大佬本来可以满分的,因为这个扣了200分),在非必须的情况下尽量不要用(当然时间卡的特别紧输入又多的毒瘤题另当别论)

今天的题不像往常多,只有四道,往常我的博客很少写上所有的题,但是今天都写上吧,就当是做个纪念.

 在这次考试之中这道题也算是一道比较简单的题了,从题意之中我们可以读出每个点只有一个出边,于是我们不用去考虑环套环的情况,由于消息穿一圈又回到自己一定是个环,于是考虑用tarjan求出图里面的强连通分量的大小,在特判掉大小为一的连通分量的前提下求出最小的连通分量的大小就行了(写这么少不是我想水,而是这道题真的没什么说的QAQ);

 1 #include<cstdio>
 2 #include<stack>
 3 #include<cstring>
 4 #include<algorithm>
 5 const int N=4e5+10;
 6 using namespace std;
 7 struct Node{
 8     int next,to;
 9 }edge[N];
10 int Head[N],tot;
11 void Add(int x,int y){
12     edge[++tot].to=y;
13     edge[tot].next=Head[x];
14     Head[x]=tot;
15 }
16 stack<int>sta;
17 int dfn[N],low[N],dfn_cnt,scc_cnt,siz[N],belong[N];
18 void tarjan(int u){
19     dfn[u]=low[u]=++dfn_cnt;
20     sta.push(u);
21     for(int i=Head[u];i;i=edge[i].next){
22         int v=edge[i].to;
23         if(!dfn[v]){
24             tarjan(v);
25             low[u]=min(low[u],low[v]);
26         }
27         else if(!belong[v]) low[u]=min(low[u],dfn[v]);
28     }
29     if(dfn[u]==low[u]){
30         scc_cnt++;
31         int t;
32         do{
33             t=sta.top();sta.pop();
34             belong[t]=scc_cnt;
35             siz[scc_cnt]++;
36         }while(t!=u);
37     }
38 }
39 int main(){
40     //freopen("a.in","r",stdin);
41     //freopen("a.out","w",stdout);
42     int n;
43     scanf("%d",&n);
44     for(int i=1;i<=n;++i){
45         int x;
46         scanf("%d",&x);
47         Add(i,x);
48     }
49     for(int i=1;i<=n;++i){
50         if(!dfn[i]) tarjan(i);
51     }
52     int ans=0x3f3f3f3f;
53     for(int i=1;i<=scc_cnt;++i){
54         if(siz[i]!=1) ans=min(ans,siz[i]);
55     }
56     printf("%d\n",ans);
57     return 0;
58 }
信息传递

 这是这次考试之中我唯一没有做对也是我最想吐槽的一道题,在最近dp的强化训练之下,我成功地联想到了树形dp,然后想到一个dp[i][j]来表示以i为父亲的点和j之间的边被卡掉的答案总和,然后成功的只得了40分,后来在输出中间变量时发现我的思路可能会让同一深度下的路径同时断掉两条,我的思路只考虑了最优,没有考虑所能断掉的边的限制,超出了蓬莱国的承受能力(~滑稽).

其实这道题的正解就是暴力,我其实也挺惊讶,暴力的思路其实我想过,但是大概模拟了一遍时间效率之后就放弃了,没有考虑优化,我算的最坏的情况下能跑到2^100左右这个数longlong都开不下,但是优化之后可以快很多,远远超出了我的想象,guo大佬说其实优化之后的时间效率也是(O玄学)的,谁让这道题的数据小呢,它就过了.

考虑暴搜,首先预处理出一个深度的所有儿子,之后在进入下一层之前先把一条边上的所有点都标记一下,出来后再去下标记,如果当前深度已经超过了最大深度或是一层之内没有了被感染的点,就停止递归,这里可以有一个剪枝优化,如果当前递归出的答案已经大于了最小答案,直接终止即可,可以说这个一行简单的优化撑起了这道时间效率玄学的题的时间复杂度.

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 const int N=300+5,Inf=2147483647;
 5 int n,m,max_depth,ans=2147483647;
 6 using namespace std;
 7 int depth[N],head[N],Cut[N],f[N],deep[N][N],cnt[N];
 8 int tot;
 9 struct Edge{
10     int next,to;
11 }edge[N<<1];
12 void Add(int u,int v){
13     edge[++tot].to=v;
14     edge[tot].next=head[u];
15     head[u]=tot;
16 }
17 void dfs(int now,int fa){
18     for(int i=head[now];i;i=edge[i].next){
19         int v=edge[i].to;
20         if(v==fa) continue;
21         depth[v]=depth[now]+1;
22         f[v]=now;
23         max_depth=max(max_depth,depth[v]);
24         dfs(v,now);
25     }
26 }
27 void tag(int u,int color){
28     Cut[u]=color;
29     for(int i=head[u];i;i=edge[i].next){
30         int v=edge[i].to;
31         if(v==f[u]) continue;
32         Cut[v]=color;
33         tag(v,color);
34     }
35 }
36 int calc(int dep){
37     int sum=0;
38     for(int i=1;i<=cnt[dep];i++)
39         if(Cut[deep[dep][i]]==0)
40             sum++;
41     return sum;
42 }
43 void Search(int dep,int sum){
44     if(sum>=ans) return;
45     if(dep>max_depth||calc(dep)==0){
46         ans=min(ans,sum);
47         return;
48     }
49     for(int i=1;i<=cnt[dep];i++){
50         int to=deep[dep][i];
51         if(Cut[to]==1) continue;
52         tag(to,1);
53         Search(dep+1,sum+calc(dep));
54         tag(to,0);
55     }
56 }
57 void Solve(){
58     scanf("%d%d",&n,&m);
59     for(int i=1;i<=m;i++){
60         int u,v;scanf("%d%d",&u,&v);
61         Add(u,v);Add(v,u);
62     }
63     dfs(1,0);
64     for(int i=1;i<=n;i++)
65         deep[depth[i]][++cnt[depth[i]]]=i;
66     Search(1,1);
67     printf("%d\n",ans);
68 }
69 int main(){
70     Solve();
71     return 0;
72 }
传染病控制

 这道题做过的,但是这一点连教练也没认识到,可能教练钉钉时代也在划水没有认真做题吧......

看到这个数据范围首先想到状压DP和暴搜,其实好多人都是暴搜过的,不知道我的为什么挂了,辛亏考场上自己造了一组极限大数据及时认识到了这一点,然后边写边拍写出了一个状压dp,才擦边过了这道题(我们学校数据太过于水,洛谷上只能拿80分,其余超时).暴搜就不怎么详细的说明了,相信大家都会,就是从0出发依次向里面放数字,同时记录模值,为零则记录答案.

如果是DP的话(状压,虽然一些大佬写的数位dp也能过,跑的比我还快),由于我们的答案和当前放的数字的状态,当前情况的模数有关,考虑构造dp[i][j]表示状态为i模数为j时候的答案,于是存在转移方程:dp[i|(1<<(j-1))][(k*10+a[j])%d]+=dp[i][k],由于在运算中的取模操作可以先算后模也可以先模后算,所以第二维可以这样进行枚举,因为输入的数字可能存在重复的情况,考虑对最后的答案除以每个数字出现次数的阶乘,这样算相信对刚刚学完排列组合的我们很好理解,阶乘我们可以提前处理,时间开销主要在dp的过程上,但是已经比暴搜快很多了,可用.

 1 #include<cstdio>
 2 #include<stack>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<vector>
 6 #include<iostream>
 7 #include<bitset>
 8 #define debug printf("-debug-\n")
 9 typedef long long ll;
10 using namespace std;
11 char c[15];
12 int a[15],A[15],num[15],len;
13 int d;
14 ll ans;
15 void dfs(int pos,int t,int Mod){
16     if(pos==len+1&&Mod==0){
17         ans++;return;
18     }
19     if(pos>len+1) return;
20     for(int i=1;i<=len;++i){
21         if(t&(1<<(i-1))) continue; 
22         dfs(pos+1,t|(1<<(i-1)),(Mod*10+a[i]+d)%d);
23     }
24 }
25 int main(){
26     //freopen("a.in","r",stdin);
27     //freopen("a.out","w",stdout);
28     int t;
29     scanf("%d",&t);
30     while(t--){
31         ans=0;
32         scanf(" %s%d",c,&d);
33         len=strlen(c);
34         for(int i=0;i<=9;++i){
35             A[i]=1;num[i]=0;
36         }
37         for(int i=0;i<len;++i){
38             a[i+1]=c[i]-'0';
39             num[a[i+1]]++;A[a[i+1]]*=num[a[i+1]];
40         }
41         dfs(1,0,0);
42         for(int i=0;i<=9;++i) ans/=A[i];
43         printf("%lld\n",ans);
44     }
45     return 0;
46 }
暴搜50分
 1 #include<cstdio>
 2 #include<stack>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<vector>
 6 #define debug printf("-debug-\n")
 7 using namespace std;
 8 char c[15];
 9 int dp[1<<11][1500];
10 int a[15],A[15],num[15];
11 void Solve(){ //状压!!状压!!!
12     int d;
13     scanf(" %s%d",c,&d);
14     int len=strlen(c);
15     for(int i=0;i<15;++i){
16         A[i]=1;num[i]=0;//预处理阶乘数组 
17     }
18     for(int i=0;i<len;++i){
19         a[i+1]=c[i]-'0';
20         num[a[i+1]]++;A[a[i+1]]*=num[a[i+1]]; //计算数据的阶乘 
21     }
22     memset(dp,0,sizeof(dp));
23     dp[0][0]=1;
24     int ed=(1<<len)-1;
25     for(int i=0;i<=ed;++i)
26         for(int k=0;k<d;++k)
27             for(int j=1;j<=len;++j){
28                 if((i&(1<<(j-1)))) continue;
29                 dp[i|(1<<(j-1))][(k*10+a[j])%d]+=dp[i][k];
30                 //printf("%d %d\n",dp[i|(1<<(j-1))][(k*10+a[j])%d],dp[i][k]);
31             }
32     for(int i=0;i<=9;++i) dp[ed][0]/=A[i];//去重过程 
33     printf("%d\n",dp[ed][0]);
34 }
35 int main(){
36     //freopen("a.in","r",stdin);
37     //freopen("a.out","w",stdout);
38     int t;
39     scanf("%d",&t);
40     while(t--) Solve();
41     return 0;
42 }
排列_状压

 刚看到这道题时是真的觉得它送分,所以当成绩出来时看到它爆零时我才会觉得无法接受,弄得我中午吃完饭后赶紧回来调一下,这不调还好,调完后我比原来还要气愤,啊啊啊,这个用getchar读入没有判掉'\r'就零分是神马情况,老师的评测机不是linxu吗,怎么会有windows的'\r'

然后这道题就没有什么说的了,比平常的线段树还省去了建树的过程,其实这道题唯一需要注意的一点就是在进行修改和查询的时候总区间要写成从1到m,如果写的是到当前输入的数组的长度的话是不行的,我们可以考虑线段树的维护过程,层数是logM的,如果你只到数组长度的话前面输入的值就会吊在线段树的中间,和后面的数比较的过程就会受阻,样例都过不去

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 const int N=2e5+10;
 5 #define debug printf("-debug-\n")
 6 #define lson (t<<1)
 7 #define rson (t<<1|1)
 8 #define mid ((l+r)>>1)
 9 typedef long long ll;
10 using namespace std;
11 ll tree[N<<2],a[N];
12 int n;
13 ll Mod;
14 void pushup(int t){
15     tree[t]=max(tree[lson],tree[rson])%Mod;
16 }
17 void change(int t,int l,int r,int pos,ll num){
18     if(l==r){
19         tree[t]=num%Mod;
20         return;
21     }
22     if(mid>=pos) change(lson,l,mid,pos,num);
23     else change(rson,mid+1,r,pos,num);
24     pushup(t);
25 }
26 ll query(int t,int l,int r,int ql,int qr){
27     if(ql<=l&&r<=qr){
28         return tree[t]%Mod;
29     }
30     if(l>qr||r<ql) return 0;
31     ll ans=0;
32     if(ql<=mid) ans=max(ans,query(lson,l,mid,ql,qr))%Mod;
33     if(qr>mid) ans=max(ans,query(rson,mid+1,r,ql,qr))%Mod;
34     return ans%Mod; 
35 }
36 char read(){
37     char ch=' ';
38     while(ch==' '||ch=='\n'||ch=='\r') ch=getchar();
39     return ch;
40 }
41 int main(){
42     //freopen("a.in","r",stdin);
43     //freopen("a.out","w",stdout);
44     scanf("%d%lld",&n,&Mod);
45     int len=0;
46     ll pre=0;
47     for(int i=1;i<=n;++i){
48         char ch=read();ll x;
49         scanf("%lld",&x);
50         if(ch=='A'){
51             change(1,1,n,++len,(pre+x)%Mod);
52         }
53         if(ch=='Q'){
54             int l=len-x+1;
55             pre=query(1,1,n,l,len)%Mod;
56             printf("%lld\n",pre);
57         }
58     }
59     return 0;
60 }
最大数

其实根据大佬所言用单调队列也是可以的,我们维护最后的队列是值单调递减,下表单调递增的,在查找答案时直接二分查找就行了.

推一下老师的博客:https://www.cnblogs.com/hbhszxyb/p/13194239.html

额外题目及总结晚上再补吧,现在先这样吧,下午任务还有做一道紫题没有完成呢.

猜你喜欢

转载自www.cnblogs.com/li-jia-hao/p/13195610.html