20181103 考试记录
这是$NOIP$模拟赛(---)
T1:
想的太复杂了,开头$1$个小时认为此题不可做,所以到最后也懒得打$O(n)$的方法,只打了$O(n^2)$的东西
我的做法:
先把数据的图建出来,然后查看对于每个列是否没产生矛盾
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<map> #include<algorithm> using namespace std; inline int read(){ int f=1,ans=0;char c; while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } int vis[300001],n,has[300001],a[300001],b[300001],c[300001],lb[300001],lc[300001]; struct node{ int u,v,nex; }x[1010001]; int ma[300001]; int cnt,head[300001]; void add(int u,int v){ x[cnt].u=u,x[cnt].v=v,x[cnt].nex=head[u],head[u]=cnt++; } int ba[300001],ca[300001],ans; int bb[101],cc[101],minn=2<<30-1,aa[101],book[101]; void dfs(int pos){ if(pos>=1&&pos<=n) ma[++ma[0]]=pos; for(int i=head[pos];i!=-1;i=x[i].nex){ if(!vis[x[i].v]){ vis[x[i].v]=1; dfs(x[i].v); } } } void check(){ int ans=0; aa[0]=0,bb[0]=0,cc[0]=0; for(int i=1;i<=n;i++){ if(book[i]==1){ aa[++aa[0]]=a[i]; bb[++bb[0]]=b[i]; cc[++cc[0]]=c[i]; ans++; } } sort(aa+1,aa+aa[0]+1); sort(bb+1,bb+bb[0]+1); sort(cc+1,cc+cc[0]+1); for(int i=1;i<=aa[0];i++){ if(aa[i]==bb[i]&&aa[i]==cc[i]&&bb[i]==cc[i]){ continue; }return ; } minn=min(minn,n-ans); return; } void dfs1(int pos){ if(pos==n+1){ check();return; } book[pos]=1; dfs1(pos+1); book[pos]=0; dfs1(pos+1); book[pos]=0; } int main(){ // freopen("avogadro.in","r",stdin); // freopen("avogadro.out","w",stdout); memset(head,-1,sizeof(head)); n=read(); for(int i=1;i<=n;i++) a[i]=read(),has[a[i]]=i; for(int i=1;i<=n;i++) b[i]=read(),lb[i]=has[b[i]],add(i+n,lb[i]); for(int i=1;i<=n;i++) c[i]=read(),lc[i]=has[c[i]],add(i+2*n,lc[i]); if(n<=10){ dfs1(1); cout<<minn; return 0; } for(int i=1;i<=n;i++) add(i,i+n),add(i,i+2*n); for(int i=1;i<=n;i++){ ma[0]=0; memset(ba,0,sizeof(ba)),memset(ca,0,sizeof(ca)); memset(vis,0,sizeof(vis)); vis[i]=1; dfs(i); for(int j=1;j<=ma[0];j++) ba[b[ma[j]]]++; for(int j=1;j<=ma[0];j++) ca[c[ma[j]]]++; for(int j=1;j<=ma[0];j++){ if(ba[b[ma[j]]]!=1||ca[c[ma[j]]]!=1){ ans++; break; } } } cout<<ans; return 0; } /* 9 1 3 5 9 8 6 2 4 7 2 1 5 6 4 9 3 4 7 3 5 1 9 8 6 2 8 7 */
别人的做法:
垃圾搜索。其实要抓住第一列是不重复的数这一特点去解题。若第$2,3$列有没有出现而在第$1$行出现的时候,则这一列必须删去,有可能又有不存在的数,就再去删除,时间复杂度$O(n)$,因为每个列只会最多删去一次。并且可以去先按照第一行排序,这样就方便查询了。
正确性:因为有小于$0$的就必有$\leq 2$的数,而这就恰好可以去进行搜索了,但是如果去先统计$\leq 2$的点,就会产生冲突,因为不知道该删哪个
T2:
好像以前讲过类似的题,但是却不会写,自己的暴力也还挂了,看到$n \leq 9$想到什么,搜索 状压啊,但在考场上还不会写。
我们设$dp(i,S)$表示已经要决策到第i条边,点的集合划分为$S$($n!$状压),每个点都能在当前的集合中互相到达的概率是多少。
所以分两种情况讨论:
若第i条边是好的,则$dp(i,合并当前的2个集合)=dp(i-1,S)*(1-{p_i})$
若是不好的,则$dp(i,S)=dp(i-1,S)*(p_i)$
主要是怎么去写那个$S$
#include<algorithm> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #include<iomanip> #include<iostream> #include<map> #include<queue> #include<stack> #include<vector> #define rep(i,x,y) for(register int i=(x);i<=(y);i++) #define dwn(i,x,y) for(register int i=(x);i>=(y);i--) #define maxn 10 #define maxm 90 #define maxs 363000 using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)&&ch!='-')ch=getchar(); if(ch=='-')f=-1,ch=getchar(); while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar(); return x*f; } void write(int x) { int f=0;char ch[20]; if(!x){putchar('0'),putchar('\n');return;} if(x<0)x=-x,putchar('-'); while(x)ch[++f]=x%10+'0',x/=10; while(f)putchar(ch[f--]); putchar('\n'); } int mul[maxn],tmp[maxn],tmp2[maxn],eu[maxm],ev[maxm],n,m; long double ew[maxm],p[2][maxs]; int gx(int x,int y){return (x-1)*mul[y-1];} void bac(int S) { dwn(i,n,1) { tmp[i]=S/mul[i-1]+1; S%=mul[i-1]; } } int gets() { int s=0; rep(i,1,n) { s+=gx(tmp2[i],i); } return s; } /* int maxs; void getst(int x) { if(x==10) { int s=0; rep(i,1,9) { s+=gx(fa[i],i); } if(yes[s]){cout<<s<<"nooooooo";exit(0);} // else cout<<"+"<<s<<endl; bac(s); yes[s]=1; maxs=max(maxs,s); return; } rep(i,1,x) { fa[x]=i; getst(x+1); } }*/ int main() { freopen("connect.in","r",stdin); freopen("connect.out","w",stdout); mul[0]=1; rep(i,1,9)mul[i]=mul[i-1]*i; //getst(1); //cout<<maxs; n=read(),m=read(); rep(i,1,m) { eu[i]=read(),ev[i]=read(); if(eu[i]>ev[i])swap(eu[i],ev[i]); double w;scanf("%lf",&w); ew[i]=w; } rep(i,1,n)tmp2[i]=i; int nows=gets(); p[0][nows]=1.0; //cout<<nows<<endl; rep(i,1,m) { //cout<<"i:"<<i<<endl; int now=i&1,pre=now^1; rep(u,0,nows)p[now][u]=0; rep(u,0,nows) { if(p[pre][u]==0.0000)continue; bac(u);//cout<<"tmp:"<<p[i][u]<<endl; //rep(j,1,n)cout<<tmp[j]<<" ";cout<<endl; int ss=tmp[eu[i]],tt=tmp[ev[i]]; if(ss==tt){p[now][u]+=p[pre][u];continue;} if(ss>tt)swap(ss,tt); rep(j,1,n) { if(tmp[j]==tt)tmp2[j]=ss; else tmp2[j]=tmp[j]; } int nxt=gets(); // cout<<"nxt:"; //rep(j,1,n)cout<<tmp2[j]<<" "; //cout<<endl; p[now][nxt]+=p[pre][u]*(1.0-ew[i]); p[now][u]+=p[pre][u]*ew[i]; //cout<<p[i][nxt]<<endl; } } double ans=p[m&1][0]; printf("%.3lf",ans); return 0; }
T3:斜率优化$dp$,但是没有学,所以留坑待补
$score:60+40+40=140$这分数是要完的节奏啊
接下来的是CXM讲课的几道题
T1:
平面上有$n \leq 3 \times 10^5$的店,坐标$\leq 10^5$,点上有能量,且只能往直右或直上走,每走一次需要有$k$的能量($k \leq 10^3$),问最大能量
我们只要将怎样去快速转移达到就行了,所以我们采取从下到上,从左往右的顺序去$dp$,每一行内的答案可以去记录,每一列的答案可以用个表去更新,记录
T2:
有$n$个与$m$个单词$(\leq 3\times 10^5)$,每个单词$30$个字符,问每个单词的暴力查找次数($you$ $guess$ 这是什么意思)
建一棵$Trie$树,若$m$中的一个单词$p$在$n$中没有出现过,那么答案就很好统计,否则,我们可以都扫描过一遍,然后看一看是$n$中第几个单词,然后在建一棵$Trie$树,维护一下历史版本的答案,每次统计完答案在加入字符串,最后一块输出即可