每道题的难度大概都在noip提升组Day2T2T3难度c++
只会模拟git
模拟写完以后,小小加了一个优化编程
指望得分20+20数据结构
光是理解题意就花了好长时间。。。学习
而后勉勉强强算是会了暴力的写法(复杂度\(O(N!)\))优化
不会判重,可是lyq说按照他的想法这题根本不须要判重???spa
我。。。这题完全理解错误了debug
四联通是指对于每一个格子向外扩展的方向3d
而实际主角能够每次对一个联通块进行操做使其变色rest
还觉得每次只能处理特定形状的部分。。。。
GG
/* * 考虑根据题意模拟 * 对每次询问都进行[l,r]内的mod操做,取mod后最大值 * 大概有20分 */ #include <cstdio> #include <algorithm> inline int read() { int n=0,w=1;register char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar(); return n*w; } inline int max(int x,int y) {return x>y?x:y;} const int N=1e5+1; int n,m,a[N];//,ans[N]; /*struct Query{ int l,r,k,id; bool operator<(const Query &x)const {return k<x.k;} }que[N];*/ int main() { freopen("flower.in","r",stdin); freopen("flower.out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;++i) a[i]=read(); for(int ans,l,r,k,i=1;i<=m;++i) { ans=0; l=read(),r=read(),k=read(); for(;l<=r;++l) ans=max(ans,(a[l]>=k?a[l]%k:a[l])); printf("%d\n",ans); } fclose(stdin);fclose(stdout); return 0; }
/* * 尝试理解题意 * 。。。 * 理解无能。。。 * * 等会好像是dfs的方式 * 判重不会搞。 * 那就不搞了. */ #include <stack> #include <cstdio> inline int read() { int n=0,w=1;register char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar(); return n*w; } const int N=6001,mod=1e9+7; int n,ans,x[N],y[N],lx[N],topx,ly[N],topy; void dfs() { for(int i=1;i<=n;++i) if(y[i]<ly[topy] && (x[i]<lx[topx] && x[i]>lx[topx-1]) || (x[i]>lx[topx] && x[i]<lx[topx-1])) { ly[++topy]=y[i]; lx[++topx]=x[i]; dfs(); --topx,--topy; } ++ans; if(ans==mod)ans=0; } void dfsbegin() { for(int i=1;i<=n;++i) if(y[i]<ly[topy]) { ly[++topy]=y[i]; lx[++topx]=x[i]; dfs(); --topx,--topy; } ++ans; } int main() { freopen("refract.in","r",stdin); freopen("refract.out","w",stdout); n=read(); for(int i=1;i<=n;++i) x[i]=read(),y[i]=read(); for(int i=1;i<=n;++i) { ly[++topy]=y[i]; lx[++topx]=x[i]; dfsbegin(); --topx,--topy; } printf("%d",ans); fclose(stdin);fclose(stdout); return 0; }
/* * 谁能告诉我样例是怎么出来的。。。 */ #include <cstdio> #include <cstring> const int N=51,dx[]={0,1,0,-1},dy[]={1,0,-1,0}; int r,c,ans,sum,color[N][N]; bool map[N][N]; void dfs(int x,int y) { color[x][y]=sum; for(int xx,yy,i=0;i<4;++i) { xx=x+dx[i],yy=y+dy[i]; if(xx<1 || xx>r || yy<1 || yy<c)continue; dfs(xx,yy); } } void work() { for(int i=1;i<=r;++i) for(int j=1;j<=c;++j) if(true); } int main() { freopen("paint.in","r",stdin); freopen("paint.out","w",stdout); scanf("%d%d",&r,&c); int tot=0; for(int x,i=1;i<=r;++i) for(int j=1;j<=c;++j) { scanf("%d",x); map[i][j]=x; if(!x)++tot; } /*if(r<=15 && c<=15) { for(int i=1;i<=r;++i) for(int j=1;j<=c;++j) if(!color[i][j]) { ++sum; dfs(i,j); } } else*/ if(r==1) { for(int i=1;i<=c;++i) if(map[1][i]!=map[1][i-1] && map[1][i]!=map[1][i+1]) map[1][i]=map[1][i-1],++ans; for(int i=1;i<=c;++i) if(map[1][i]) ++++i,++ans; printf("%d",ans); } else printf("%d",tot>4?tot>>1:tot); fclose(stdin);fclose(stdout); return 0; }
\(20\)分\(n^2\)作法
\(40\)分块
另外\(20\)分的作法求前缀和,查看某个数字是否存在
当\(k\)比较大的时候就主席树,当\(k\)比较小的时候由于主席树复杂度会爆,用上面另\(20\)分作法
真·正解:
考虑当\(k\)肯定的时候如何求解,显然对于全部形如\([a_k,(a+1)k)\)的值域,最大值必定是最优的
进一步观察发现,这样的区间总数只有\(k\times \ln k\)个,考虑分块,那么咱们能够在\(O(n+k\ln k)\)的时间复杂度内处理出一个块对于任意\(k\)的答案,询问的时候复杂度是\(O(mS),(S\text{是块的大小})\)的,取\(S=\sqrt{k\ln k}\)能够达到最优复杂度\(O(n\sqrt{k\ln k})\)
#include <bits/stdc++.h> using std::pair; using std::vector; using std::string; typedef long long ll; typedef pair<int, int> pii; #define fst first #define snd second #define pb(a) push_back(a) #define mp(a, b) std::make_pair(a, b) #define debug(...) fprintf(stderr, __VA_ARGS__) template <typename T> bool chkmax(T& a, T b) { return a < b ? a = b, 1 : 0; } template <typename T> bool chkmin(T& a, T b) { return a > b ? a = b, 1 : 0; } const int oo = 0x3f3f3f3f; string procStatus() { std::ifstream t("/proc/self/status"); return string(std::istreambuf_iterator<char>(t),std::istreambuf_iterator<char>()); } template <typename T> T read(T& x) { int f = 1; x = 0; char ch = getchar(); for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1; for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48; return x *= f; } const int B = 1000; const int N = 100000; int n, q; int a[N + 5]; int lst[N + 5]; int ans[105][N + 5]; int main() { //freopen("flower.in", "r", stdin); //freopen("flower.out", "w", stdout); read(n); read(q); for(int i = 0; i < n; ++i) read(a[i]); int blks = (n-1) / B + 1; for(int i = 0; i < blks; ++i) { memset(lst, 0, sizeof lst); for(int j = i * B; j < (i+1) * B && j < n; ++j) lst[a[j]] = a[j]; for(int j = 1; j <= N; ++j) chkmax(lst[j], lst[j-1]); for(int j = 1; j <= N; ++j) for(int k = 0; k <= N; k += j) chkmax(ans[i][j], lst[std::min(k + j - 1, N)] - k); } while(q--) { static int l, r, k; read(l), read(r), read(k); -- l, -- r; int x = (l / B) + 1, y = (r / B), res = 0; if(x == y + 1) { for(int i = l; i <= r; ++i) chkmax(res, a[i] % k); } else { for(int i = x; i < y; ++i) chkmax(res, ans[i][k]); for(int i = l; i < x*B; ++i) chkmax(res, a[i] % k); for(int i = r; i >=y*B; --i) chkmax(res, a[i] % k); } printf("%d\n", res); } return 0; }
10分和20分其实都是\(n^3\)作法,区别仅在于常数问题
50分是\(n^2\)的\(dp\),可是由于空间开不下就变成了\(50\)分
100分:上面50分的dp把空间卡成\(\frac{n^2}{2}\)就是100分了
\(dp[i][j]\)表示上一条折线是\(i\rightarrow j\)的
具体地讲:
若将全部点按照\(y_i\)的顺序进行转移,有上界和下界两个限制,优化比较难
那么考虑按照\(x_i\)排序进行转移,而且记录\(f_{i,0/1}\)表示以第\(i\)个点为顶端接下来向左或者向右的折线方案数,从左到右加点
考虑前\(i\)个点构成的包含\(i\)点的折线,因为新加入的点横坐标是当前考虑的点钟横坐标最大的,因此只多是折线的起始点或者第二个点
如图,假设新加入点\(i\)是折线的起始点,\(k\)是一个符合转移条件的点(也就是在\(i\)点下方),显然可行
如图,\(i\)是当前点,\(j\)是上一个点,\(k\)是一个符合条件的点(在\(i\)点左下方,同时在\(j\)点右下方)
那么有
第二种状况能够进行前缀和优化,复杂度\(O(n^2)\)
#include <bits/stdc++.h> using std::pair; using std::vector; using std::string; typedef long long ll; typedef pair<int, int> pii; #define fst first #define snd second #define pb(a) push_back(a) #define mp(a, b) std::make_pair(a, b) #define debug(...) fprintf(stderr, __VA_ARGS__) template <typename T> bool chkmax(T& a, T b) { return a < b ? a = b, 1 : 0; } template <typename T> bool chkmin(T& a, T b) { return a > b ? a = b, 1 : 0; } const int oo = 0x3f3f3f3f; string procStatus() { std::ifstream t("/proc/self/status"); return string(std::istreambuf_iterator<char>(t),std::istreambuf_iterator<char>()); } template <typename T> T read(T& x) { int f = 1; x = 0; char ch = getchar(); for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1; for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48; return x *= f; } const int N = 6000; const int mo = 1e9 + 7; pii p[N + 5]; int dp[N + 5][2], n; int main() { //freopen("refract.in", "r", stdin); //freopen("refract.out", "w", stdout); read(n); for(int i = 1; i <= n; i++) { read(p[i].fst), read(p[i].snd); } sort(p + 1, p + n + 1); for(int i = 1; i <= n; i++) { dp[i][0] = dp[i][1] = 1; for(int j = i-1; j >= 1; j--) { if(p[j].snd > p[i].snd) { (dp[j][1] += dp[i][0]) %= mo; }else { (dp[i][0] += dp[j][1]) %= mo; } } } int ans = mo - n; for(int i = 1; i <= n; i++) ans = ((ans + dp[i][0]) % mo + dp[i][1]) % mo; printf("%d\n", ans); return 0; }
7分输出黑色联通块个数
26分状压DP
民间100分作法:每一个联通块和它相邻的联通块连边,最后发现是一个图,若是每一个联通块只能在图中出现一次,就会变成一棵树
而后找最长链
正解作法:
能够发现一个“比较显然”的结论:存在一种最优方案使得每次操做的区域是上一次的子集且颜色和上一次相反
考虑概括法证实:
那么咱们能够枚举最后被修改的区域,这时答案就是将同色边边权当成\(0\),异色边边权为\(1\),然后举例这个点最远的黑色点的距离,对全部点取最小值便可
#include <bits/stdc++.h> using std::pair; using std::vector; using std::string; typedef long long ll; typedef pair<int, int> pii; #define fst first #define snd second #define pb(a) push_back(a) #define mp(a, b) std::make_pair(a, b) #define debug(...) fprintf(stderr, __VA_ARGS__) template <typename T> bool chkmax(T& a, T b) { return a < b ? a = b, 1 : 0; } template <typename T> bool chkmin(T& a, T b) { return a > b ? a = b, 1 : 0; } const int oo = 0x3f3f3f3f; string procStatus() { std::ifstream t("/proc/self/status"); return string(std::istreambuf_iterator<char>(t),std::istreambuf_iterator<char>()); } template <typename T> T read(T& x) { int f = 1; x = 0; char ch = getchar(); for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1; for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48; return x *= f; } const int N = 50; int n, m; char g[N + 5][N + 5]; int dis[N + 5][N + 5]; const int dx[] = { 1, 0, -1, 0 }; const int dy[] = { 0, 1, 0, -1 }; int bfs(int x, int y) { std::deque<pii> q; memset(dis, -1, sizeof dis); dis[x][y] = 0; q.push_back(mp(x, y)); int res = 0; while(!q.empty()) { int cx = q.front().fst, cy = q.front().snd; if(g[cx][cy] == '1') chkmax(res, dis[cx][cy]); q.pop_front(); for(int i = 0; i < 4; ++i) { int nx = cx + dx[i], ny = cy + dy[i]; if(nx >= 0 && nx < n && ny >= 0 && ny < m && dis[nx][ny] == -1) { if(g[nx][ny] == g[cx][cy]) { dis[nx][ny] = dis[cx][cy]; q.push_front(mp(nx, ny)); } else { dis[nx][ny] = dis[cx][cy] + 1; q.push_back(mp(nx, ny)); } } } } return res; } int main() { //freopen("paint.in", "r", stdin); //freopen("paint.out", "w", stdout); read(n), read(m); for(int i = 0; i < n; ++i) { scanf("%s", g[i]); } int ans = oo; for(int i = 0; i < n; ++i) { for(int j = 0; j < m; ++j) { chkmin(ans, bfs(i, j)); } } printf("%d\n", ans + 1); return 0; }
平面上有一个点光源,以每秒一个单位的速度从\((x_0,y)\)沿直线走到\((x_1,y),y\lt 0\),\(x\)轴上面有\(n\)条线段会遮挡光线,\(x\)轴上方有\(q\)个点。问每一个点可以被光线直射的总时间
\(n,q\le 10^5\)
解:
反过来理解,把\(x\)轴上方的\(q\)个点看作点光源,下面移动的是接收器
那么要求的就是对于每一个点光源,其能照射到接收器的总时间
那么每一个点光源向周围发射的光通过\(x\)轴上面的线段遮挡以后到下面就造成了两个三角形(点光源和光在\(x\)轴上照亮的区间、点光源和光在移动路径上照亮的区间)
由于移动路径和\(x\)轴是平行的
考虑根据类似,先求出每一个点在\(x\)轴上照亮的区间,再根据比值就求出了在移动路径上的长度
另外因为可能会出现:接收器并无彻底通过照亮的区间就中止移动了或者接收器开始移动的位置以前的部分也可以被照亮
因此对这两个区间(包含起点和终点的)进行二分,找到不合法(被照亮可是接收器没有通过这里)的区间,减掉这部分
用二分的缘由:精度差小
给出一个长度为\(n\)的序列\(\{a_i\}\),如今进行\(m\)次操做,每次操做会修改某些\(a_i\)的值,在每次操做完成以后须要判断是否存在一个位置\(i\)知足\(a_i=\sum_{j=1}^{i-1}a_j\)并求出任意一个\(i\)
\(n,m\le 10^5,0\le a_i\le 10^9\)
解:
若是是单点修改的话,考虑维护式子\(a_i=\sum_{j=1}^{i-1}a_j\),变成\(a_i-\sum_{j=1}^{i-1}a_j=0\),显然对于单点修改的状况下这个很是好维护,直接查询是否是\(0\)就好了
区间修改的话,首先考虑一个暴力,每次从\(i=1\)开始统计\(\sum_i a_i\)而后判断是否合法,显然\(sum_i\)是单调不降的,符合条件的\(a_i\)必定\(\ge sum_i\)每次用线段树找到大于等于当前前缀和的最左边的\(a_i\)而且判断是否合法
由于“若是一个新的\(a_i\)不合法,那么当前前缀和至少会达到上一次的两倍”,那么这样查找的复杂度就是\(O(\log\omega)\),\((\omega\)是前缀和的最大值\()\),那么总复杂度就是\(O(m\log n\log\omega)\)
有一棵\(n\)个点的树,每条边有两个参数\(a_i,b_i\),在某一时刻\(t\),一条边的长度为\(a_i\times t+b_i\)
问在不一样时刻\(t=0,1,2\cdots,m-1\)这些时刻树的直径
\(n,m,a_i\le 10^5,b_i\le 10^9\)
解:
考虑已经获得若干条路径后如何计算答案, 将每条路径表示成数对\((a_i,b_i)\), 从小到大枚举 \(t\) , 直径的 \(a_i\) 必定单调不降, 而且变化过程能够用 斜率优化维护.
\(a_i\times t+b_i\ge a_j\times t+b_j\rightarrow (a_i-a_j)\times t\ge b_j-b_i\)
\(-t\le \frac{b_i-b_j}{a_i-a_j}\)
发现是一个相似上凸壳的转移
可是这样的复杂度是\(O(n^2)\),复杂度依然是错的
显然咱们须要的复杂度是\(O(n\sqrt n)\)或者\(O(n\log n)\)
问题变成如何减少路径数量, 比较容易想到的方法是树分治
因为跨越中心的两部分须要合并, 咱们采用边分治
跨越中心的部分就是两个部分的点的闵科夫斯基和.
$Minkowski Sum $
定义: 给出两个点集\(A,B\), 求点集$ C ={a+b | a\in A,b\in B}$的凸包.
解法: \(C\)中凸包上的点由\(A,B\)凸包上的点相加获得, 先求出\(A,B\)的凸包. 找到\(A,B\)一组相加后在凸包上的点, 向后按照相邻向量的极角顺序合并.
给一个初始为空的整点集, 如今有\(n\)次操做:
- 1 x y向点集中插入\((x,y)\)
- 2 x y从点集中删除\((x,y)\)
- 3 x y询问至少须要添加多少个点知足这个点集造成的图像关于\((0,0),(x,y)\)的连线对称
\(n\le 2\times 10^5,0\le x,y\le 10^5\)
解:
注意到互相对称的两点到达原点的距离相同,即互相对称的两点在同一个圆的圆周上
由于这些点对都是整数,而圆上整数点的规模能够看作\(\sqrt n\)(实际远小于\(\sqrt n\),详细可见《隐藏在素数规律中的\(\pi\)》)
对于每一个圆,维护其边界上点的集合,每加入或者删除一个点就计算 这个点和其它点的对称中心并统计答案
复杂度能够认为是\(O(n\sqrt n\log n)\)可是难以达到理论上界
给出三个数\(u,v,p\),能够对\(u\)进行以下操做
- \(u\leftarrow u-1\mod p\)
- \(u\leftarrow u+1\mod p\)
- \(u\leftarrow u^{p-2}\mod p\)
求一种不超过\(200\)步的方案使得\(u\)最终变成\(v\)
\(0\le u,v\lt p\le 10^9\),\(p\)是质数
解:
操做\(3\)也就是费马小定理求逆元
由于操做\(3\)的存在, 咱们能够近似地认为这张图是随机的, 可是直接搜的指望复杂度仍是很大
考虑双向\(BFS\),先从\(u\)出发进行\(BFS\)并取出前\(\sqrt p\)个状态的值,显然这部分路径的长度不会超过\(100\)
接下来从\(v\)出发进行\(BFS\)而且在以前的答案中查询,根据生日悖论,\(\sqrt p\)次找不到解的几率接近\(0\)
生日悖论
一个班级,\(30\)我的中,有两人生日相同的几率接近\(100%\)
证实:
考虑全部人中没有人的生日相同的几率
第一我的与第二我的生日不一样的几率为\(\frac{364}{365}\),第三我的不与前两人生日相同的几率为\(\frac{363}{365}\),第四人\(\cdots\),最后当人数为\(30\)的时候,可得三十我的生日两两不一样的几率=\(1\times \frac{364}{365}\times \frac{363}{365}\times \cdots\frac{335}{365}\)
这个值通过收敛,很是接近\(0\)
由于"全部人中没有人生日相同"是"有至少两人生日相同"的对立事件
因此“至少两人生日相同“这一事件的几率是接近\(100%\)的
给出一个长度为\(n\)的序列\(x\),每一个位置有\(a_i,b_i\)两个参数,\(b_i\)互不相同,能够进行任意次以下操做:
- 若存在\(j\ne i\)知足\(a_j=a_i\),则能够花费\(b_i\)的代价让\(a_i+1\)
- 若存在\(j\)知足\(a_j+1=a_i\),则能够花费\(-b_i\)的代价让\(a_i-1\)
定义一个序列的权值为将序列中全部\(a_i\)变得互不相同所需的最小代价。
问给定序列的每个前缀的权值
\(n,a_i\le 2\times 10^5,1\le b_i\le n\)
解:
先考虑全部\(a_i\)互不相同的时候怎么作,若存在\(a_i+1=a_j\),则能够花费\(b_i-b_j\)的代价交换两个\(a_i\),显然最优方案会将序列中全部\(a_i\)连续的自断操做成按\(b_i\)降序的
若是有\(a_i\)相同,则能够先将全部\(a_i\)编程互不相同的再进行排序,可是这时候可能会扩大值域使得本来不连续的两个区间合并到一块儿,因而咱们须要维护一个支持合并的数据结构
尝试用并查集维护每一个值域连续的块,而且在每一个并查集的根上维护一个以\(b\)为关键字的值域线段树,每次合并两个联通块的时候,合并他们对应的线段树便可维护答案
有两个串\(S,T\)和一个数字\(k\),能够将\(S\)中任意两个长度为\(k\)的不相交子串取出,而且按照顺序拼接到一块儿,求一种取串的方案使得\(T\)是这个新串的子串
\(|T|\le 2k\le |S|\le 5\times 10^5\)
解:
\(T\)必定存在某个分段点使得这个点的左边部分和右边部分分别是两个不相交子串的一个后缀和前缀,考虑枚举这个分段点,那么显然只须要知足左边部分第一次出现的标号小鱼右边部分最后一次出现的标号便可
进一步观察发现,对应\(T\)的全部前缀,第一次出现的位置是单调的,咱们只须要作一次\(KMP\)便可获得美国前缀出现的最先的位置