NOIP 2014
DAY1
T1 生活大爆炸版石头剪刀布
一道简单的模拟题,给出对应的关系,要打表!打表!打表!
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; //Golbel define const int N = 210; const int map[5][5] = { { 0,-1, 1, 1,-1}, { 1, 0,-1, 1,-1}, {-1, 1, 0,-1, 1}, {-1,-1, 1, 0, 1}, { 1, 1,-1,-1, 0} }; int afi[N],bfi[N]; int n,lena,lenb,nowa,nowb,scoa,scob; int main(){ freopen("rps.in","r",stdin); freopen("rps.out","w",stdout); scanf("%d",&n); scanf("%d%d",&lena,&lenb); for(int i = 0;i < lena;++i)scanf("%d",&afi[i]); for(int i = 0;i < lenb;++i)scanf("%d",&bfi[i]); for(int i = 1;i <= n;++i){ if(nowa == lena)nowa = 0; if(nowb == lenb)nowb = 0; if(map[afi[nowa]][bfi[nowb]] == 1)scoa++; else if(map[afi[nowa]][bfi[nowb]] == -1)scob++; nowa++; nowb++; } printf("%d %d\n",scoa,scob); return 0; }
T2 联合权值
求出距离为2的所有点的权值的乘积的和的两倍,枚举中点,直接算所有点的积,显然超时,那么计算所有点(a+b+c+d+.....+n)^2-(a^2-b^2+c^2+d^2+...+n^2)
#include <cstdio> #include <iostream> #include <cstring> #include <vector> #include <algorithm> using namespace std; //Golbel define const int N = 200010; const int mod = 10007; vector <int> edge[N]; int n,u,v,w[N],ans,maxi; //end Golebel define int main(){ freopen("link.in","r",stdin); freopen("link.out","w",stdout); scanf("%d",&n); for(int i = 1;i < n;++i){ scanf("%d%d",&u,&v); edge[u].push_back(v); edge[v].push_back(u); } for(int i = 1;i <= n;++i){ scanf("%d",&w[i]); } for(int i = 1;i <= n;++i){ int size = edge[i].size(); int sum = 0,tot = 0,ret = 0;//sum = {a+b+c...+n}^2 tot = {a^2+b^2+c^2...+n^2} int maxfirst = 0,maxsecond = 0; for(int j = 0;j < size;++j){ int v = edge[i][j]; if(w[v]>maxfirst){ maxsecond = maxfirst; maxfirst = w[v]; } else if(w[v]>maxsecond){ maxsecond = w[v]; } sum = (sum%mod + w[v]%mod)%mod; tot = (tot%mod + (w[v]*w[v])%mod)%mod; } maxi = max(maxi,(maxfirst*maxsecond)); ret = ( ( (sum*sum)%mod-tot%mod )%mod+mod)%mod; (ans += ret) %= mod; } printf("%d %d\n",maxi,ans); return 0; }
T3 飞扬的小鸟
定义状态dp[i][j]为在(i,j)至少按的次数
总共有两种情况{
一.上升:1.上升一步 dp[i][j] = min{dp[i-1][j-x[i-]]+1}
2.上升多步 dp[i][j] = min{dp[i-1][j-k*x[i-1]]+k}这样是会超时的,因为有重叠子问题,那么先考虑飞的情况后考虑坠的情况,那么由下面的点转移而来
min{dp[i][j-x[i-1]]+1}。
3.上升到图的最高点并且只一步dp[i][m] = min{dp[i-1][k]+1}(m-x[i-1]<=k<=m)
4.上升到图的最高点多步的情况dp[i][m] = min(dp[i][k]+1)
二.下降:dp[i][j] = min{dp[i-1][j+y[i-1]]}
}
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; //Golbel define const int inf = 0x3fffffff; const int M = 1010; const int N = 10010; int n,m,k; int dp[N][M]; int fly[N],fal[N],up[N],down[N],x[N]; //end Golbel defien int main(){ freopen("bird.in","r",stdin); freopen("bird.out","w",stdout); scanf("%d%d%d",&n,&m,&k); for(int i = 0;i < n;++i){ scanf("%d%d",&fly[i],&fal[i]); } for(int i = 1;i <= n;++i){ up[i] = m; down[i] = 1; } for(int i = 1;i <= k;++i){ int l,h; scanf("%d%d%d",&x[i],&l,&h); down[x[i]] = l+1;up[x[i]] = h-1; } for(int i = 0;i <= n;++i){ for(int j = 0;j <= m;++j){ dp[i][j] = inf; } } for(int i = 1;i <= m;++i)dp[0][i] = 0; for(int i = 1;i <= n;++i){ for(int j = 1;j <= m;++j){ if(j > fly[i-1]){ dp[i][j] = min(dp[i-1][j-fly[i-1]]+1,dp[i][j]); dp[i][j] = min(dp[i][j-fly[i-1]]+1,dp[i][j]); } if(j == m){ for(int k = m-fly[i-1];k <= m;++k){ dp[i][j] = min(dp[i][j],dp[i-1][k]+1); dp[i][j] = min(dp[i][j],dp[i][k]+1); } } } for(int j = down[i];j <= up[i];++j){ if(j+fal[i-1] <= m){ dp[i][j] = min(dp[i-1][j+fal[i-1]],dp[i][j]); } } for(int j = 0;j < down[i];++j)dp[i][j] = inf; for(int j = up[i]+1;j <= m;++j)dp[i][j] = inf; } int ans = inf; for(int i = 1;i <= m;++i)ans = min(ans,dp[n][i]); if(ans != inf){ printf("1\n%d\n",ans); } else{ puts("0"); int cnt = 0,flag = false; for(int i = n-1;i >= 0;--i){ if(flag)break; for(int j = down[i];j <= up[i];++j){ if(dp[i][j] != inf){ flag = true; cnt = i; break; } } } sort(x+1,x+k+1); for(int i = 1;i <= k;++i){ if(cnt < x[i]){ printf("%d\n",i-1); break; } } } return 0; }
DAY2
T1 无线网络发射器选址
暴力枚举每一个地址,暴力计算,注意边界,也可以用前缀和优化,也可以套二维树状数组。
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define LL long long const int N = 200; LL map[N][N],sum[N][N]; int main(){ freopen("wireless.in","r",stdin); freopen("wireless.out","w",stdout); int n,d,x,y; LL k; scanf("%d%d",&d,&n); for(int i = 1;i <= n;++i){ scanf("%d%d%I64d",&x,&y,&k); map[x][y] = k; } LL ansi = 0,maxi = -1; for(int i = 0;i <= 128;++i){ for(int j = 0;j <= 128;++j){ LL cnt = 0; for(int a = max(0,i-d);a <= min(i+d,128);++a){ for(int b = max(0,j-d);b <= min(j+d,128);++b){ cnt += map[a][b]; } } if(cnt > maxi){ maxi = cnt; ansi = 1; } else if(cnt == maxi){ ansi++; } } } cout<<ansi<<" "<<maxi<<endl; return 0; }
T2 寻找道路
问一条特殊的最短路,特殊在于每一点所连的点都能够到达终点。从终点反向一次搜索把能够到达终点的点都标记下来,再正向一次枚举若标记的点可以到达没有标记的点,那么这个点就取消标记,最后一次Dijkstra最短路。
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int N = 10010; const int M = 800010; const int inf = 1<<30; int n,m,x,y,s,t; struct edge{ int v,w; edge *nxt; }*cur,*head[N],*back_head[N],meo[M]; struct node{ int dis,u; bool operator < (const node &rhs)const{ return dis >rhs.dis; } }; int mark[N],done[N],dis[N],vis[N]; void adde(int u,int v,edge *f[]){ cur->v = v; cur->w = 1; cur->nxt = f[u]; f[u] = cur++; } void dfs(int x){ mark[x] = 1; for(edge *it = back_head[x];it;it = it->nxt){ int v = it->v; if(!mark[v])dfs(v); } } void Dijkstra(int s){ priority_queue <node> q; for(int i = 1;i <= n;++i)dis[i] = inf; dis[s] = 0; q.push((node){dis[s],s}); while(!q.empty()){ node p = q.top();q.pop(); int u = p.u; if(done[u])continue; done[u] = true; for(edge *it = head[u];it;it = it->nxt){ int v = it->v; if(dis[v] > dis[u]+it->w && mark[v] == 1){ dis[v] = dis[u]+it->w; q.push((node){dis[v],v}); } } } } int main(){ freopen("road.in","r",stdin); freopen("road.out","w",stdout); cur = meo; scanf("%d%d",&n,&m); for(int i = 1;i <= m;++i){ scanf("%d%d",&x,&y); adde(x,y,head); adde(y,x,back_head); } scanf("%d%d",&s,&t); memset(mark,0,sizeof(mark)); dfs(t); for(int i = 1;i <= n;++i){ if(mark[i]){ for(edge *it = head[i];it;it = it->nxt){ int v = it->v; if(!mark[v]){ mark[i] = -1;//不能为0 break; } } } } Dijkstra(s); if(dis[t] == inf)puts("-1"); else printf("%d\n",dis[t]); return 0; }
T3 解方程
f(x) = a[0]+a[1]*x+a[2]*x^2+a[3]*x^3+......a[n]*x^n = 0;
既然f(x) = 0,0%p = 0,那么f(x)%p = 0,这里运用hash的思想,这是不对的想法但是错误的概率很小,类似的费马小定理的逆用是相似的,那么很容易想到的是一个O(n)枚举所有在范围里面的解,但是是要超时的,既然f(x)%p = 0,那么很显然f(x+p)%p = 0,那么就先预处理出,x在0~p-1的范围的数,大于其的数直接%p来判断方程是否为0。
类似于Hash要选择素数。
附加神犇vfk的想法:
我们可以选一个好取模的素数比如2^31−1=2147483647。
由于 a⋅2^31+b≡a+b(mod2^31−1),所以 x 与 (x & 0x7fffffff) + (x >> 31) 是同余的!
register unsigned long long y = 0; for (int i = n; i >= 0; i--) { y = y * x + a[i]; y = (y & 0x7fffffff) + (y >> 31); } y %= 0x7fffffff;
这样就跑得快了三倍了!(从1.2s变成了0.4s)
在此基础上再多加几个奇怪的素数模一模判一判就好了。只要不是零多项式那么至多只有n个根,所以后面不加什么常数优化也没关系。
献上我的代码:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <map> using namespace std; #define LL long long const int N = 110; const int mod1 = 38833; const int mod2 = 998244353; const int M = 1000010; char s[10010]; int n,m,a[M],ans[M],cnt,b[M],first[M],second[M]; int main(){ freopen("equation.in","r",stdin); freopen("equation.out","w",stdout); scanf("%d%d",&n,&m); for(int i = 0;i <= n;++i){ scanf("%s",s); int x1 = 0,x2 = 0,flag = 1; if(s[0] == '-')flag = -1; else { x1 = s[0]-'0'; x2 = s[0]-'0'; } for(int i = 1;i < strlen(s);++i){ x1 = (x1*10+s[i]-'0')%mod1; x2 = ((LL)x2*10+s[i]-'0')%mod2; } a[i] = x1,b[i] = x2; if(~flag){ a[i] = -x1; b[i] = -x2; } } for(int i = 0;i < mod1;++i){ ans[i] = a[n]; for(int j = n-1;~j;--j)ans[i] = (ans[i]*i+a[j])%mod1; } for(int i = 1;i <= m;++i){ if(!ans[i%mod1])first[++(*first)] = i;//f(x+p)%p = 0 -> f(x)%p = 0 } for(int i = 1;i <= *first;++i){ int x = first[i];ans[i] = b[n]; for(int j = n-1;~j;--j)ans[i] = ((LL)ans[i]*x+b[j])%mod2; } for(int i = 1;i <= *first;++i)if(!ans[i])second[++(*second)] = first[i]; printf("%d\n",second[0]); for (int i = 1; i <= second[0]; ++i) printf("%d\n", second[i]); return 0; }