NOIP2014提高组 题解报告
D1
T1 无线网路发射器选址
题目大意:找一个矩形,使其覆盖的目标点最大。
题目过水,直接暴力搞过去,代码就不贴了。
但我TM居然有个地方SB了,调了半天才发现输入有问题:
scanf("%d%d%d",&x,&y,&t[x][y]);//这是我原来的写法.....
T2 寻找道路
题目大意:给你一个有向图,找一条从起点到终点的最短路径,且路径上的所有点的出边所指向的点都直接或间接与终点连通。
先处理出所有满足条件的点,然后在上面跑死了的即可。
注意这里我们不是直接去找与终点相连的点,因为这样会漏掉一些点(别问我怎么知道的)。
所以我们可以用到不了终点的点去跟新其它点即可。
const int N=2e5+5; vector<int>G1[N],G2[N]; //G1正图 G2反图 bool T[N],f[N]; int n,m,x,y,s,t,d[N]; queue<int>q; inline void bfs() { q.push(t);T[t]=f[t]=1; while(!q.empty()) { int x=q.front();q.pop(); for(rg int i=0;i<G2[x].size();++i) { int v=G2[x][i]; if(!T[v]) T[v]=f[v]=1,q.push(v); } } for(rg int i=1;i<=n;++i) if(!T[i]) for(rg int j=0;j<G2[i].size();++j) { int v=G2[i][j]; if(f[v]) f[v]=0; } while(!q.empty()) q.pop(); q.push(s);memset(d,0x3f,sizeof d);d[s]=0; while(!q.empty()) { int x=q.front();q.pop(); for(rg int i=0;i<G1[x].size();++i) { int v=G1[x][i]; if(f[v] && d[v]>d[x]+1) { d[v]=d[x]+1; q.push(v); } } } }
T3 解方程
题目大意:求给定一元次方程在上的整数解。
我最开始还以为要打高精度,其实可以用秦九韶算法来解决。
这个算法可以将一元次多项式的求值问题转化为个一次式的算法,然后……自己看度娘吧
总之,这个算法可以让我们在的时间内解决该问题。(过程中可以随便膜一个数如192*****来简化运算)
#include<cstdio> #include<vector> using namespace std; #define int long long const int p=19260817; int n,m,a[105]; vector<int>Ans; inline int read()//骚气读入 { int f=1,x=0;register char c=getchar(); while(c>'9' || c<'0') {if(c=='-') f=-1;c=getchar();} while(c>='0' && c<='9') x=((x<<3)+(x<<1)+(c^'0'))%p,c=getchar(); return x*f%p;//记得取膜 } signed main() { n=read(),m=read(); for(int i=0;i<=n;++i) a[i]=read(); for(int i=1;i<=m;++i) { int ans=0; for(int j=n;j>=1;--j) ans=(ans+a[j])*i%p; ans=(ans+a[0])%p; if(!ans) Ans.push_back(i); } printf("%lld\n",(int)Ans.size()); for(int i=0;i<(int)Ans.size();++i) printf("%lld\n",Ans[i]); return 0; }
D2
T1 生活大爆炸版石头剪刀布
题目大意:石头剪刀布
巨水的模拟,贴一下代码就行了~~
#include<cstdio> using namespace std; const int N=205; int n,na,nb,a[N],b[N],c[5][5],cnta,cntb; //0表示“剪刀”,1表示“石头”,2表示“布”,3表示“蜥蜴人”, 4表示“斯波克 int main() { scanf("%d%d%d",&n,&na,&nb); for(int i=1;i<=na;++i) scanf("%d",&a[i]); for(int i=1;i<=nb;++i) scanf("%d",&b[i]); c[1][0]=c[2][1]=c[0][2]=c[0][3]=c[1][3]=c[2][4]=c[3][4]=c[3][2]=c[4][0]=c[4][1]=1; for(int i=1;i<=n;++i) { int A=i%na,B=i%nb; if(!A) A=na; if(!B) B=nb; // printf("%d %d\n",a[A],b[B]); cnta+=c[a[A]][b[B]],cntb+=c[b[B]][a[A]]; // printf("cnt:%d %d\n",cnta,cntb); } printf("%d %d",cnta,cntb); return 0; }
T2 联合权值
题目大意:给你一颗树,边权均为,每个点有点权(),求 以及()
直接 暴力枚举是肯定过不了的,要想一些奇技淫巧来优化。
要求之间距离为,又是在树上,那么它们之间必然有个中转点。于是我们可以枚举该点。
但的范围巨大,怎么能高效地跟新答案呢?这里有一个神奇的公式:
设 , 则有
因为我们加的是双向边,所以对于一个节点,与它相连的所有点之间都可以互相构成联合权值
将其表示出来:
- 令为与它相连的所有点的权,那么任意一个点能形成的联合权值为
- 那么以该点为中转点所形成的联合权值,为...这不就是上式吗?于是,我们就得到了一个优秀的 做法。
#include<cstdio> #include<vector> #include<algorithm> #define rg register #define ll long long struct ios{ template<typename TP> inline ios operator >> (TP &x) { TP f=1;x=0;rg char c=getchar(); for(;c>'9' || c<'0';c=getchar()) if(c=='-') f=-1; for(;c>='0' && c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); x*=f; return *this; } template<typename TP> inline ios operator << (TP x) { char s[66];rg int cnt=0; if(!x) putchar('0'); if(x<0) x=-x,putchar('-'); while(x) ++cnt,s[cnt]=x%10+'0',x/=10; while(cnt) putchar(s[cnt]),--cnt; return *this; } inline ios operator << (char x) { putchar(x); return *this; } }io; const int N=2e5+5; std::vector<int>G[N]; const int p=10007; int n,a,b,w[N],ans,sum; int main() { io>>n; for(rg int i=1;i<n;++i) { io>>a>>b; G[a].push_back(b),G[b].push_back(a); } for(rg int i=1;i<=n;++i) io>>w[i]; // for(rg int i=1;i<=n;++i) io<<w[i]<<' '; // io<<'\n'; /*--------------------------以某个节点为中转点的联合权值之和等于权值和的平方减去权值的平方和----------------------*/ for(rg int i=1;i<=n;++i) { int max_1=0,max_2=0; int he=0,pf=0; for(int k=0;k<(int)G[i].size();++k) { int v=G[i][k]; // io<<i<<' '<<G[i].size()<<' '<<k<<' '<<v<<'\n'; if(w[v]>max_1) max_2=max_1,max_1=w[v]; else if(w[v]>max_2) max_2=w[v]; he=(he+w[v])%p,pf=(pf+w[v]*w[v])%p; } he=he*he%p; // io<<'h'<<'h'<<max_1<<' '<<max_2<<'\n'; ans=std::max(ans,max_1*max_2); sum=(sum+he-pf+p)%p;//注意he-pf可能为负数 } io<<ans<<' '<<sum; return 0; }
T3 飞扬的小鸟
题目大意:(首先,请确保你知道)给你每个位置点击上升的距离和不点击下降的距离、每个管道的位置。求能不能通过以及如果能通过,最少点击数为多少。
很显然可以看出,这题是(求最优解,数据范围比较大,还不能贪心,除了,还真就没有做法了)
- 用表示横坐标为时高度为的最少点击次数,用来表示不可能达到这个状态。
- 每个管道的位置不能走,其他地方就按照规则来走,注意小鸟撞到天花板不会挂,只有高度为是才会挂,所以特判一下。
#include<cstdio> #include<algorithm> #include<cmath> #define rg register #define ll long long #define cg c=getchar() using namespace std; struct ios{ template<typename TP> inline ios operator >> (TP &x) { TP f=1;x=0;rg char cg; for(;c>'9' || c<'0';cg) if(c=='-') f=-1; for(;c>='0' && c<='9';cg) x=(x<<3)+(x<<1)+(c^'0'); x*=f; return *this; } template<typename TP> inline ios operator << (TP x) { char s[66];rg int cnt=0; if(x<0) x=-x,putchar('-'); if(!x) putchar('0'); while(x) s[++cnt]=x%10+'0',x/=10; while(cnt) putchar(s[cnt--]); return *this; } inline ios operator << (char s) { putchar(s); return *this; } }io; const int N=1e4; const int M=1e3; const int inf=0x3f3f3f3f; int n,m,k,cnt=1; struct node{ int x,y; }a[N]; struct Node{ int pos,L,H; inline bool operator < (const Node b) const { return pos<b.pos; } }g[N]; int dp[N][M],vis[N][M]; int main() { io>>n>>m>>k; for(rg int i=0;i<n;++i) io>>a[i].x>>a[i].y; for(rg int i=1;i<=k;++i) { io>>g[i].pos>>g[i].L>>g[i].H; for(int j=1;j<=g[i].L;++j) vis[g[i].pos][j]=1; for(int j=g[i].H;j<=m;++j) vis[g[i].pos][j]=1; } sort(g+1,g+k+1); //io<<'h'<<'h'<<'h'<<'\n'; for(rg int i=1;i<=n;++i) for(rg int j=1;j<=m;++j) dp[i][j]=inf; for(rg int i=1;i<=n;++i) { // io<<'h'<<'h'<<'h'<<'\n'; int L=1,R=m; if(g[cnt].pos==i) L=g[cnt].L+1,R=g[cnt].H-1,++cnt; // if(i==5) io<<L<<' '<<R<<'\n'; int x=a[i-1].x,y=a[i-1].y; for(rg int j=L;j<=R;++j) { if(j==m) { for(rg int l=1;l<=m;++l) { if(!vis[i-1][l]) { int w=ceil(1.0*(m-l)/x); if(j==l) w=1; dp[i][j]=min(dp[i][j],dp[i-1][l]+w); } } continue; } // if(i==5) io<<j+y<<' '<<y<<'\n'; if(j+y<=m && !vis[i-1][j+y]) dp[i][j]=min(dp[i][j],dp[i-1][j+y]); int w=j/x; for(rg int l=1;l<=w;++l) if(j-x*l>0 && !vis[i-1][j-x*l]) dp[i][j]=min(dp[i][j],dp[i-1][j-x*l]+l); } // io<<'h'<<'h'<<'h'<<'\n'; } int ans=inf; // for(int i=m;i>=0;--i) // // for(int i = 0; i <= n; ++i) // { // // for (int j = 0; j <= m; ++j) // for(int j=0;j<=n;++j) // { // if(dp[j][i]==inf) io<<'*'<<' '; // else io<<dp[j][i]<<' '; // } // io<<'\n'; // } for(rg int i=1;i<=m;++i) ans=min(ans,dp[n][i]); if(ans==inf) { io<<0<<'\n'; for(rg int i=1;i<=k;++i) { int p=g[i].pos; for(rg int j=1;j<=m;++j) { if(dp[p][j]!=inf) break; if(j==m) { io<<i-1<<'\n'; return 0; } } } } io<<1<<'\n'<<ans; return 0; }
然鹅你发现这个达不到上界的的只能水分。(~~不过联赛时有分也不错了~~)
那该如何优化呢?
我们发现,小鸟每次上升多次,但只能下降一次(保证不超边界时)。
上升多次,不就相当于是多重背包吗?
那么下降一次,就可以看做背包。
有了这个思路,我们就可以成功地把复杂度将为了
#include<cstdio> #include<cstring> #include<algorithm> #define rg register #define ll long long #define cg c=getchar() using namespace std; struct ios{ template<typename TP> inline ios operator >> (TP &x) { TP f=1;x=0;rg char cg; for(;c>'9' || c<'0';cg) if(c=='-') f=-1; for(;c>='0' && c<='9';cg) x=(x<<3)+(x<<1)+(c^'0'); x*=f; return *this; } template<typename TP> inline ios operator << (TP x) { char s[66];rg int cnt=0; if(x<0) x=-x,putchar('-'); if(!x) putchar('0'); while(x) s[++cnt]=x%10+'0',x/=10; while(cnt) putchar(s[cnt--]); return *this; } inline ios operator << (char s) { putchar(s); return *this; } }io; const int N=1e4+5; const int M=1e3+5; const int inf=0x3f3f3f3f; int n,m,k; struct node{ int x,y; }a[N]; int dp[N][M<<1],vis[N],L[N],R[N]; int main() { // freopen("1.in","r",stdin); io>>n>>m>>k; for(rg int i=1;i<=n;++i) io>>a[i].x>>a[i].y,L[i]=1,R[i]=m; for(rg int i=1;i<=k;++i) { int a,b,c;io>>a>>b>>c; vis[a]=1,L[a]=b+1,R[a]=c-1; } memset(dp,0x3f,sizeof dp); for(rg int i=1;i<=m;++i) dp[0][i]=0; for(rg int i=1;i<=n;++i) { int x=a[i].x,y=a[i].y; for(rg int j=x+1;j<=m+x;++j) dp[i][j]=min(dp[i-1][j-x]+1,dp[i][j-x]+1); for(rg int j=m+1;j<=m+x;++j) dp[i][m]=min(dp[i][m],dp[i][j]); for(rg int j=1;j+y<=m;++j) dp[i][j]=min(dp[i][j],dp[i-1][j+y]); // io<<i<<' '<<L<<' '<<R<<'\n'; for(rg int j=1;j<=L[i]-1;++j) dp[i][j]=inf; for(rg int j=R[i]+1;j<=m;++j) dp[i][j]=inf; } int ans=inf; // io<<inf<<'\n'; // for(int i=m;i>=0;--i) // // for(int i = 0; i <= n; ++i) // { // // for (int j = 0; j <= m; ++j) // for(int j=0;j<=n;++j) // { // if(dp[j][i]==inf) io<<'*'<<' '; // else io<<dp[j][i]<<' '; // } // io<<'\n'; // } for(rg int i=1;i<=m;++i) ans=min(ans,dp[n][i]); if(ans>=inf) { io<<0<<'\n'; int i,j; for(i=n;i>=1;--i) { for(j=1;j<=m;++j) { if(dp[i][j]<inf) break; } if(j<=m) break; } ans=0; for(int j=1;j<=i;++j) if(vis[j]) ++ans; io<<ans; } else io<<1<<'\n'<<ans; return 0; }