来自FallDream的博客,未经允许,请勿转载,谢谢。
-------------------------------------------------------
大家好,我是一个假人。在学习OI的过程中,我凭借自己的努力,成功发明出了大顶的dij,并且帮助自己在cf和模拟赛上多过了 -2 道题。发明算法真美妙!
--------------------------------------------------------
不说了,D题因为dij打成大顶的挂了,血都吐出来了。A题瞎特判fst了,神tm只有bc还能70多名. 都是假的。
A.给定a,b,c,d,求是否有y满足 y=a*x1+b=c*x2+d且x1,x2属于N a,b,c,d<=100
数据范围这么小,瞎暴力一下呗。
#include<iostream> #include<cstdio> #include<cmath> #include<queue> #include<cstring> #include<algorithm> #define ll long long #define INF 2000000000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int a,b,c,d; bool mark[20000000]; int main() { a=read();b=read();c=read();d=read(); for(int i=0;i<=100000;i++) mark[b+i*a]=1; for(int j=0;j<=100000;j++) if(mark[d+j*c])return 0*printf("%d",d+j*c); puts("-1"); return 0; }
B.给定n个序列,每个序列有ki个数,问是否每个序列都有两个数满足x1=-x2 n,K<=10000
题解:..............
#include<iostream> #include<cstdio> #include<cmath> #include<queue> #include<cstring> #include<algorithm> #define ll long long #define INF 2000000000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int n,m; int s[200005]; int main() { n=read();m=read(); for(int i=1;i<=m;i++) { int k=read();bool yes=false; for(int j=1;j<=k;j++) { int x=read();if(x<0)x=(-x)+n; if(i==s[(x+n-1)%(n*2)+1]){yes=true;} s[x]=i; } if(!yes)return 0*puts("YES"); } puts("NO"); return 0; }
C.有n个点,两个人。两个人分别有k1,k2个数,他们玩一个游戏。轮流进行,每个人可以选择一个数并且把棋子前移这么多格,到达1号点胜利,问两个人分别从2-n开始是否必胜,必败或者会循环。
n<=7000
题解:正向的dp明显不行,我们考虑倒着dp。一个情况必胜当且仅当能转移到一个对方必败的区域,必败当且仅当到达的所有区域都是对方必胜的。显然第一个和第二个人从一号点出发是必败的,我们开一个队列,每次都向后转移,如果目前要转移的状态是必败,那么直接把能转移到的情况改成必胜,加入队列;否则我们开两个r数组,记录一下每一个点的出度,开始为k1/k2,从必胜的情况转移到它时把它度数-1,如果它的度数变成了0,说明它肯定必败了,可以更新一下加入队列。一直这么做就可以了,最后没有被dp到的就是循环的点。
#include<iostream> #include<cstdio> #include<cmath> #include<queue> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define INF 2000000000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int f1[7005],f2[7005]; int n,k1,k2; int s[7005],s2[7005],r[7005],r2[7005]; struct node{ int x,k; }; queue<node> q; int main() { n=read();k1=read(); f1[1]=f2[1]=2; for(int i=1;i<=k1;i++) { s[i]=read()%n; } k2=read(); for(int j=1;j<=k2;j++) { s2[j]=read()%n; } for(int i=1;i<=n;i++)r[i]=k1,r2[i]=k2;r[1]=r2[1]=INF; q.push((node){1,1});q.push((node){1,2}); while(!q.empty()) { node x=q.front();q.pop(); //cout<<x.x<<" "<<x.k<<endl; if(x.k==1) { for(int i=1;i<=k2;i++) { int np=(x.x+n-s2[i]-1)%n+1; // cout<<x.x<<" "<<x.k<<" "<<f1[x.x]<<" "<<np<<" "<<endl; if(f2[np])continue; if(f1[x.x]==1) { r2[np]--; if(!r2[np]) f2[np]=3-f1[x.x],q.push((node){np,2}); } else { f2[np]=3-f1[x.x]; q.push((node){np,2}); } } } else { for(int i=1;i<=k1;i++) { int np=(x.x+n-s[i]-1)%n+1; // cout<<x.x<<" "<<x.k<<" "<<f2[x.x]<<" "<<np<<" "<<endl; if(f1[np])continue; if(f2[x.x]==1) { r[np]--; if(!r[np]) f1[np]=3-f2[x.x],q.push((node){np,1}); } else { f1[np]=3-f2[x.x]; q.push((node){np,1}); } } } } for(int i=2;i<=n;i++) { if(f1[i]==1)cout<<"Win "; else if(f1[i]==2) cout<<"Lose "; else cout<<"Loop "; } puts(""); for(int i=2;i<=n;i++) { if(f2[i]==1)cout<<"Win "; else if(f2[i]==2) cout<<"Lose "; else cout<<"Loop "; } return 0; }
D.有n个点,m条路径。每条路径可以从一个点到一个点,也可以从一个区间到一个点,也可以从一个点到一个区间,都有一定的费用。求从s号点到达其他点的最小距离。 n,m<=100000
题解:很明显题目是一道最短路,但是我们没法对区间全部连边。所以我们很容易想到开两个线段树,用最多log个点表示区间。
我们用一棵线段树,只能往上走,表示它所在的区间,另一棵线段树只能往下走,表示它走到了这个区间,第二棵树的对应节点向第一棵连边。这样之后,单点的路径直接见,点到区间的从第一个树连第二个树的对应log个节点,区间到点的直接第一棵内连。 最后,堆优化的dij, 节点最多2*2*n,复杂度4nlogn
我真tm要吐血了,还有十分钟时候打完了,交上去wa了,不服,看到两点钟,不知道哪里挂了,只好滚去睡觉。第二天迷迷糊糊就去学校了,在ditoly大神的指导下猛然发现dij写成了大顶的,改改就过了,上次也这样,我真的是#(@!*(*#&!(#
我好菜啊都不会
#include<iostream> #include<cstdio> #include<cmath> #include<queue> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define INF 200000000000000000LL #define MAXN 800000 #define MN 400000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int n,m,s,cnt=0; ll d[MAXN+5]; struct node{ ll x;int k; bool operator <(const node &y) const { return x>y.x; } }; int top,id[MAXN+5],head[MAXN+5],num[MAXN+5]; priority_queue<node> q; struct edge{ int to,next;ll w; }e[10000000]; bool mark[MAXN+5]; void ins(int f,int t,int w) { e[++cnt]=(edge){t,head[f],w};head[f]=cnt; // cout<<"ins"<<f<<" "<<t<<" "<<w<<endl; } void get(int k,int l,int r,int lt=1,int rt=n) { if(l==lt&&r==rt){id[++top]=k;return;} int mid=lt+rt>>1; if(r<=mid) get(k<<1,l,r,lt,mid); else if(l>mid) get(k<<1|1,l,r,mid+1,rt); else {get(k<<1,l,mid,lt,mid);get(k<<1|1,mid+1,r,mid+1,rt);} } void dij() { d[num[s]]=0;q.push((node){0,num[s]}); while(!q.empty()) { node now=q.top();q.pop(); if(mark[now.k]) continue;mark[now.k]=1; for(int i=head[now.k];i;i=e[i].next) if(d[now.k]+e[i].w<d[e[i].to]) { d[e[i].to]=d[now.k]+e[i].w; q.push((node){d[e[i].to],e[i].to}); } } } void build(int k,int l,int r) { ins(k+MN,k,0); // cout<<"build"<<k<<" "<<l<<" "<<r<<endl; if(l==r) {num[l]=k;return;} int mid=l+r>>1; ins(k<<1,k,0);ins(k<<1|1,k,0); ins(k+MN,(k<<1)+MN,0);ins(k+MN,(k<<1|1)+MN,0); build(k<<1,l,mid);build(k<<1|1,mid+1,r); } int main() { n=read();m=read();s=read();memset(d,127,sizeof(d)); build(1,1,n); for(int i=1;i<=m;i++) { int t=read(); if(t==1) {int l=read(),r=read(),w=read();ins(num[l],num[r]+MN,w);} else { int v=read(),l=read(),r=read(),w=read(); top=0;get(1,l,r); for(int j=1;j<=top;j++) if(t==2) ins(num[v],id[j]+MN,w); else ins(id[j],num[v]+MN,w); } } dij(); for(int i=1;i<=n;i++) d[num[i]]=min(d[num[i]],d[num[i]+MN]); for(int i=1;i<=n;i++)if(d[num[i]]>=INF)d[num[i]]=-1; for(int i=1;i<=n;i++) printf("%lld ",d[num[i]]); return 0; }
E.给定n个数si,你要对这个序列分段,并且对于每个k(1<=k<=n)求出每段最多有k种不同的数的时候的最小分段数。 1<=si<=n<=100000
做法1:我们考虑每次暴力跳,跳的次数最多n*(1+1/2+1/3+1/4....+1/n)是nlogn,跳的方法我们可以用主席树倒着插入来维护对于每一个节点,后面的有贡献的节点,这样的话我们每次主席树上找k大就可以啦,复杂度nlog^2n
#include<iostream> #include<cstdio> #include<cmath> #include<queue> #include<cstring> #include<queue> #include<algorithm> #define MN 5000000 #define MM 100000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int n,cnt=0; struct TREE{ int l,r,x; }T[MN+5]; int s[MM+5],size[MM+5],rt[MM+5],last[MM+5]; void ins(int x,int nx,int k,int ad) { int mid,l=1,r=n;T[nx].x=T[x].x+ad; while(l<r) { mid=l+r>>1; if(k<=mid) { T[nx].r=T[x].r;T[nx].l=++cnt; x=T[x].l;nx=T[nx].l;r=mid; } else { T[nx].l=T[x].l;T[nx].r=++cnt; x=T[x].r;nx=T[nx].r;l=mid+1; } T[nx].x=T[x].x+ad; // cout<<"ins"<<x<<" "<<nx<<" "<<k<<" "<<ad<<endl; } } int query(int x,int rk,int l=1,int r=n) { //cout<<"query"<<x<<" "<<rk<<" "<<l<<" "<<r<<" "<<T[T[x].l].x<<endl; if(l==r) return l; int mid=l+r>>1; if(rk<=T[T[x].l].x) return query(T[x].l,rk,l,mid); else return query(T[x].r,rk-T[T[x].l].x,mid+1,r); } int main() { n=read(); for(int i=1;i<=n;i++)s[i]=read(); for(int i=n;i;i--) { rt[i]=++cnt;size[i]=size[i+1]+1; if(last[s[i]]) { int x=++cnt; size[i]--;ins(rt[i+1],x,last[s[i]],-1); ins(x,rt[i],i,1); } else ins(rt[i+1],rt[i],i,1); last[s[i]]=i; } for(int i=1;i<=n;i++) { int pos=1,ans=0; while(pos<=n) { ans++; if(size[pos]<=i)pos=n+1; else pos=query(rt[pos],i+1); // cout<<pos<<" "<<ans<<endl;getchar(); } printf("%d ",ans); } return 0; }
做法2:暴力算前根号n个,然后剩下的分最多根号n个块,每一块内算出所有数字的出现次数,然后你会得到n^0.5条分界线,你每次暴力推这些分界线,一条分界线最多被推n次,复杂度n^1.5
#include<iostream> #include<cstdio> #include<cmath> #include<queue> #include<cstring> #include<queue> #include<algorithm> #define DITOLY 320 #define MN 100000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int num[DITOLY+5][MN+5],s[MN+5],n,cnt=0,tot=1,size,mark[MN+5],g[MN+5]; int answer[MN+5],type[DITOLY+5]; int main() { n=read();size=sqrt(n); for(int i=1;i<=n;i++)s[i]=read(); for(int j=1;j<=size;j++) { int kind=0,ans=0;++tot; for(int i=1;i<=n;i++) { if(mark[s[i]]==tot) {if(j==size)num[cnt+1][s[i]]++;continue;} mark[s[i]]=tot;kind++; if(kind>j){if(j==size)g[++cnt]=i-1;ans++;kind=1;++tot;mark[s[i]]=tot;}; if(j==size) num[cnt+1][s[i]]++,type[cnt+1]++; } if(kind) ans++;if(j==size)g[++cnt]=n; printf("%d ",ans); } for(int i=size+1;i<=n;i++) { for(int j=1;j<cnt;j++) { if(g[j]>=n) {cnt=j+1;break;} while(g[j]<n&&type[j]+(num[j][s[g[j]+1]]==0)<=i) { g[j]++;if(++num[j][s[g[j]]]==1) type[j]++; if(!--num[j+1][s[g[j]]]) type[j+1]--; } } while(cnt>1&&g[cnt-1]==n) cnt--; printf("%d ",cnt); } return 0; }