2019上海icpc区域赛补题
A:
问题简述:平面上有n个黑格子,求有多少个黑色格子围成的矩形的边长是1:2 或者2:1。
所需知识点:树状数组,离散化。
问题形式化描述:给定n个黑格点,问黑格子围成的边长是1:2或2:1的矩形有多少个。
解题思路:我们把xy坐标旋转一下就可以把2:1的情况转为1:2的情况。然后我们发现有边长是1:2这个限制,所以我们可以先离散化,预处理出每个黑格子向四个方向最长延伸距离是多少。然后我们考虑枚举每一个斜率为2的直线,在该直线上枚举矩形的右上角,统计有多少个点满足作为该矩形的左下角的黑格子,满足条件是这个点的最上延长能够到达枚举的右上角。所以问题就变成了一个数轴上(就是枚举的直线上),有若干个原有区间(矩形左下角的点),然后查询区间[ l , r ] 中有多少个原有区间满足左端点在 [ l , r ] 内, 右端点在 r 右面。 我们可以把询问区间拆成 询问 [ 1, r ] 和 [ 1, l-1] 中,有多少个原有区间,左端点在询问区间内,右端点 在 r 右面。 这个可以用扫描线加树状数组来维护即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N = 1e6+9; 5 const ll base = 2e9; 6 struct Point{ 7 int x,y; 8 bool ri; 9 }p[N]; 10 struct Option{ 11 int l , r; 12 int ty; 13 bool operator < (const Option& b)const{ 14 return l == b.l ? ty < b.ty : l < b.l; 15 } 16 }; 17 bool test; 18 int n; 19 int tr[2*N]; 20 int X[2*N]; 21 vector<Point> vec[2*N]; 22 vector< Option > opt; 23 vector<int> use; 24 struct HashTable{ 25 const static int md = 2000003; 26 struct Edge{ 27 ll key; int val, nxt; 28 } edge[2*N]; 29 int head[md], rub[2*N], cnt, top; 30 void init(){ 31 32 while(top) head[rub[top--]] = 0; 33 cnt = 0; 34 } 35 void add(ll key, int val){ 36 37 int u = abs(key % md); 38 edge[++cnt] = {key, val, head[u]}; head[u] = cnt; 39 rub[++top] = u; 40 } 41 int fin(ll key){ 42 43 int u = abs(key % md); 44 for(int i = head[u]; i; i = edge[i].nxt){ 45 46 if(edge[i].key == key) return edge[i].val; 47 } 48 return 0; 49 } 50 void ins(ll key, int val){ 51 52 if(!fin(key)) add(key, val); 53 } 54 } le,ri,up,dw,mp; 55 bool cmpx(Point &a,Point &b){ 56 return a.x == b.x ? a.y < b.y : a.x < b.x; 57 } 58 bool cmpy(Point &a,Point &b){ 59 return a.y == b.y ? a.x < b.x : a.y < b.y; 60 } 61 //树状数组 62 void add(int x,int v,int mx){ 63 for(int i = x;i;i-=i&-i) tr[i] += v; 64 } 65 void clear(int x,int mx){ 66 for(int i = x;i<=mx;i+=i&-i) tr[i] = 0; 67 } 68 int query(int x,int mx){ 69 int res =0 ; 70 for(int i = x;i<=mx;i += i&-i) res += tr[i]; 71 return res; 72 } 73 //处理每个黑格点向四个方向的最长延长多少 74 void init(){ 75 le.init() ; ri.init(); 76 up.init() ; dw.init(); 77 mp.init(); 78 sort(p+1,p+1+n,cmpx); 79 for(int i = 1;i<=n;++i){ 80 if( int tmp = le.fin((p[i].x-1) * base + p[i].y) ) le.ins(p[i].x*base+p[i].y, tmp + 1); 81 else le.ins(p[i].x * base + p[i].y, 1); 82 } 83 for(int i = n;i>=1;--i){ 84 if( int tmp = ri.fin((p[i].x+1)*base+p[i].y) ) ri.ins(p[i].x*base+p[i].y, tmp + 1); 85 else ri.ins(p[i].x*base+p[i].y, 1); 86 } 87 sort(p+1,p+1+n,cmpy); 88 for(int i = 1;i<=n;++i){ 89 if( int tmp = dw.fin(p[i].x*base+p[i].y-1) ) dw.ins(p[i].x*base+p[i].y, tmp + 1); 90 else dw.ins(p[i].x*base+p[i].y, 1); 91 } 92 for(int i = n;i>=1;--i){ 93 if( int tmp = up.fin(p[i].x*base+p[i].y+1) ) up.ins(p[i].x*base+p[i].y, tmp + 1); 94 else up.ins(p[i].x*base+p[i].y, 1); 95 } 96 } 97 //计算每一条直线的答案 98 ll work(vector<Point> &vec){ 99 opt.clear(); 100 int cnt = 0; 101 for(auto it : vec){ 102 ll x = it.x , y = it.y; 103 //假如该点是某个格点的右上角 104 if( it.ri ){ 105 int nle = le.fin(x*base+y); int ndw = dw.fin(x*base+y); 106 Option tem; 107 int len = min(nle,ndw/2); 108 tem.l = x - len -1 ; tem.r = x ; tem.ty = -1; 109 opt.push_back(tem); 110 111 tem.l = x - 1; tem.r = x; tem.ty = 1; 112 opt.push_back(tem); 113 } 114 else{ 115 int nri = ri.fin((x+1)*base+y+1); int nup = up.fin((x+1)*base+y+1); 116 int len = min(nri,nup/2); 117 Option tem; 118 tem.l = x; tem.r = x + len; tem.ty = -2; 119 opt.push_back(tem); 120 } // 左下角 121 122 123 } 124 for(auto& it : opt){ 125 X[++cnt] = it.l; X[++cnt] = it.r; 126 } 127 sort(X+1,X+1+cnt); 128 cnt = unique(X+1,X+1+cnt) - X - 1; 129 for(auto& it : opt){ 130 it.l = lower_bound(X+1,X+1+cnt,it.l) - X; 131 it.r = lower_bound(X+1,X+1+cnt,it.r) - X; 132 } 133 ll res = 0; 134 use.clear(); 135 sort(opt.begin(),opt.end()); 136 for(auto it : opt){ 137 int l = it.l , r = it.r , ty = it.ty; 138 if( ty == -2){ 139 add(it.r,1,cnt); 140 use.push_back(it.r); 141 } 142 else{ 143 int tem = query(it.r,cnt); 144 res += ty*tem; 145 } 146 } 147 for(auto it : use){ 148 clear(it,cnt); 149 } 150 return res; 151 } 152 ll solve(){ 153 ll res = 0; int tot = 0; 154 init(); 155 for(int i = 1;i<=n;++i){ 156 Point tem = p[i]; 157 tem.ri = 1; 158 ll val = tem.y - 2ll * tem.x; 159 if(!mp.fin(val)) mp.ins(val, ++tot); 160 vec[mp.fin(val)].push_back(tem); 161 --tem.x; --tem.y; 162 tem.ri = 0; 163 val = tem.y - 2ll * tem.x; 164 if(!mp.fin(val)) mp.ins(val, ++tot); 165 vec[mp.fin(val)].push_back(tem); 166 } 167 for(int i = 1; i <= tot; ++i) res += work(vec[i]), vec[i].clear(); 168 return res; 169 } 170 const int maxs = 1e6 + 5; 171 char buf[maxs], *p1 = buf, *p2 = buf; 172 inline char fr(){ 173 174 return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, maxs, stdin)) == p1 ? -1 : *p1++; 175 } 176 #define gc fr() 177 inline void read(int &x){ 178 179 char ch; while(!isdigit(ch = gc)); x = ch ^ 48; 180 while(isdigit(ch = gc)) x = x * 10 + (ch ^ 48); 181 } 182 int main(){ 183 int T; read(T); 184 for(int cas = 1;cas <= T; ++cas){ 185 read(n); 186 ll ans = 0; int x, y; 187 for(int i = 1;i<=n;++i){ 188 read(p[i].x), read(p[i].y); 189 } 190 ans += solve(); 191 //旋转xy坐标来统计2:1的情况 192 for(int i = 1;i<=n;++i){ 193 int tx = p[i].x,ty = p[i].y; 194 p[i].x = -ty; 195 p[i].y = tx; 196 } 197 ans += solve(); 198 printf("Case #%d: %lld\n",cas,ans); 199 } 200 return 0; 201 }
B:
问题简述:给你n个长度最多为10 的 数字字符串,问是否存在某个字符串是另一个字符串的前缀。
所需知识点:字典树
问题形式化描述:使用某个数据结构,统计所有字符串,并且支持快速查询统计的字符串当中是否存在字符串是当前查询串的前缀。
解题思路:对所欲给定的n个字符串建立一颗字典树,并且对 每个字符串的末尾节点 的值加一,即每一个节点维护有多少个字符串以当前节点为结尾。对每一个字符串,我们在字典树上遍历,假如当前字符串遍历的节点(除了末节点)当中存在值不是0的节点,或者末尾节点的值大于1, 则存在,否则则不存在。
参考代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 1e4+9; 4 string a[N]; 5 struct Node{ 6 int v; 7 int son[11]; 8 }tr[N*10]; 9 int tot; 10 void ins(string s){ 11 int n = s.size(); 12 int now = 1; 13 for(int i = 0;i<n;++i){ 14 int w = s[i] - '0'; 15 if(tr[now].son[w] == 0) tr[now].son[w] = ++tot; 16 now = tr[now].son[w]; 17 } 18 tr[now].v++; 19 } 20 bool que(string s){ 21 int n = s.size(); 22 int now = 1; 23 for(int i = 0;i<n-1;++i){ 24 int w = s[i] - '0'; 25 now = tr[now].son[w]; 26 if(tr[now].v) return 0; 27 } 28 now = tr[now].son[s[n-1]-'0']; 29 if(tr[now].v>1) return 0; 30 return 1; 31 } 32 int main(){ 33 int T; cin>>T; 34 for(int cas = 1;cas<=T;++cas){ 35 tot = 1; 36 int n; cin>>n; 37 for(int i = 1;i<=n;++i) cin>>a[i]; 38 for(int i = 1;i<=n;++i) ins(a[i]); 39 bool ok = 1; 40 for(int i = 1;i<=n && ok;++i){ 41 if(!que(a[i])) ok = 0; 42 } 43 cout<<"Case #"<<cas<<": "<<(ok?"Yes":"No")<<endl; 44 for(int i = 1;i<=tot;++i){ 45 for(int j = 0;j<=9;++j) tr[i].son[j] = 0; 46 tr[i].v = 0; 47 } 48 } 49 return 0; 50 }
E:
问题简述:有n*m的格子,每个格子都有魔法值值,第i行第j列的值为 V[i][j] = X[ (i-1) * m + j ] ,其中Xi=(A×Xi−1+B×Xi−2+C) module P。当且仅当格子a和b相邻,才能从a走到b,并且假如a和b都没有被经过,可以获得a的魔法值和b的魔法值的乘积, 给定起点和终点,问从起点走到终点可获得最大魔法值是多少。(格子可以重复经过,只不过重复经过的时候魔法值乘积不算进答案)。( n,m<1e3)
所需知识点:最小生成树算法(卡鲁斯卡尔算法),并查集。
问题形式化描述:n*m的格子当中,每个格子都有值,两个相邻的格子的边权是两格子的值的乘积,求从起点走到终点的最大边权和。一条边的边权算进答案当且仅当两点都没访问过。
解题思路:对图上的所有边的边权求出来,一共会有2e6条边。对所有边按照边权从大到小排序,从大到小枚举每一条边。假如当前边的两个端点不是同一个集合,就把这条边的边权加进答案,并且把两个端点的集合并起来。
参考代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N = 1e6+9; 5 ll X[N]; 6 struct Edge{ 7 int u,v; 8 ll w; 9 bool operator < (const Edge& b)const{ 10 return w > b.w; 11 } 12 }e[N*2]; 13 int f[N]; 14 int find(int x){ 15 if(x==f[x]) return x; 16 return f[x] = find(f[x]); 17 } 18 int main(){ 19 int T; scanf("%d",&T); 20 for(int cas = 1;cas<=T;++cas){ 21 int n,m,sr,sc,tr,tc; scanf("%d %d %d %d %d %d",&n,&m,&sr,&sc,&tr,&tc); 22 ll A,B,C,P; 23 scanf("%lld %lld %lld %lld %lld %lld",&X[1],&X[2],&A,&B,&C,&P); 24 f[1] = 1; f[2] = 2; 25 for(int i = 3;i<=n*m;++i){ 26 X[i] = ( A * X[i-1] + B * X[i-2] + C) % P; 27 f[i] = i; 28 } 29 int cnt = 0; 30 for(int i= 1;i<=n;++i){ 31 for(int j = 1;j<m;++j){ 32 int s = (i-1)*m + j; 33 int t = (i-1)*m+j+1; 34 e[++cnt] = (Edge){ s,t,X[s]*X[t]}; 35 } 36 } 37 for(int j= 1;j<=m;++j){ 38 for(int i = 1;i<n;++i){ 39 int s = (i-1)*m + j; 40 int t = i*m + j; 41 e[++cnt] = (Edge){ s,t,X[s]*X[t]}; 42 } 43 } 44 sort(e+1,e+1+cnt); 45 int now = n*m; 46 ll ans = 0; 47 for(int i = 1;i<=cnt && now > 1;++i){ 48 int fu = find(e[i].u),fv = find(e[i].v); 49 if(fu==fv) continue; 50 ans += e[i].w; 51 f[fu] = fv; 52 --now; 53 } 54 printf("Case #%d: %lld\n",cas,ans); 55 } 56 return 0; 57 }
H:
问题简述:给你一颗带点权的树,给定一个数k,要求把这棵树分成k个子树,使得子树的权值最大值要最小。一棵树的权值定义为树的节点权值和。
所需知识点:二分,动态规划。
问题形式化描述:给定一颗带点权的树,割去k-1条边,使得k颗子树的点权和的最大值最小。
解题思路:要使得子树的最大值最小,我们可以二分最大值 lim , 问题变成判断能否割k - 1 条边使得子树最大值不超过 lim , 我们可以考虑动态规划来解决。dp[u] 代表以u为根节点的子树,割去尽可能少的边满足条件,u节点所在的子树的点权和。 最少割边其实就是最多合并,所以处理递归处理子树后, 对 所有与u相连的节点v 的dp值,从小到大进行合并,这样就可保证最多合并,最后返回割边数。二分判断条件就是割边数 <= k-1
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N = 1e5+9; 5 int n,k; 6 ll val[N]; 7 vector<int> G[N]; 8 ll dp[N]; 9 void init(){ 10 for(int i = 1;i<=n;++i){ 11 dp[i] = 0; 12 G[i].clear(); 13 } 14 } 15 int dfs(int u,int fa,ll lim){ 16 dp[u] = val[u]; 17 vector<ll> sondp; 18 int res = 0; 19 for(auto v : G[u]){ 20 if(v==fa) continue; 21 res += dfs(v,u,lim); 22 sondp.push_back(dp[v]); 23 } 24 sort(sondp.begin(),sondp.end()); 25 for(int i = 0;i<sondp.size();++i){ 26 if( dp[u] + sondp[i] <= lim) dp[u] += sondp[i]; 27 else{ 28 res += sondp.size() - i; 29 break; 30 } 31 } 32 return res; 33 } 34 bool judge(ll x){ 35 return dfs(1,0,x) <= k-1; 36 } 37 int main(){ 38 int T; scanf("%d",&T); 39 for(int cas = 1;cas<=T;++cas){ 40 scanf("%d %d",&n,&k); 41 init(); 42 for(int i = 1;i<n;++i){ 43 int u,v; scanf("%d %d",&u,&v); 44 G[u].push_back(v); 45 G[v].push_back(u); 46 } 47 ll sum = 0 , mx = -10; 48 for(int i =1;i<=n;++i){ 49 scanf("%lld",val+i); 50 sum += val[i]; 51 mx = max(mx,val[i]); 52 } 53 ll l = mx, r = sum , res; 54 while(l<=r){ 55 ll m = (l+r)>>1; 56 if(judge(m)){ 57 res = m; 58 r = m-1; 59 } 60 else l = m + 1; 61 } 62 printf("Case #%d: %lld\n",cas,res); 63 } 64 return 0; 65 }
I:
问题简述:在一个广场上有两个人,然后有两道传送门(可以视为传送门之间距离为0),现在想你安排这两个人的位置,使得一个人走到另一个人的位置的最短距离最大。
所需知识点:三分算法,计算几何的基本知识。
问题形式化描述:矩阵中有两个点之间距离是0,要你在矩阵中选两个点,最大化两点之间的最短距离。
解题思路:要使得两点之间距离最短距离最大,其中一个点就要在矩阵的顶点,另一个点要在矩阵的边界上。枚举矩形的四个顶点作为第一个点,再枚举另一个点是在矩形的哪一条边上,可以看到两个传送门会把这一条边分成三部分,而从一个点到另一个点的方法要么经过传送门,要么不经过,而这两种方式在每一部分是递增或者递减的,我们可以用三分算法来找到最短距离的最大值,最后取max就好了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct Point{ 4 double x,y; 5 Point operator - (const Point& b)const{ 6 return (Point){x-b.x,y-b.y}; 7 } 8 Point operator + (const Point& b)const{ 9 return (Point){x+b.x,y+b.y}; 10 } 11 Point operator / (const double& b)const{ 12 return (Point){x/b,y/b}; 13 } 14 Point operator * (const double& b)const{ 15 return (Point){x*b,y*b}; 16 } 17 }p0,ans1,ans2,p[20]; 18 double dist(Point a,Point b){ 19 return (sqrt)( (a.x - b.x) * (a.x-b.x) + (a.y - b.y) * (a.y - b.y)); 20 } 21 double ans; 22 double A,B,xa,ya,xb,yb; 23 double f(Point a,Point b,Point tar,bool show = 0){ 24 double t1 = dist(p0,tar); 25 double t2 = dist(p0,a) + dist(b,tar); 26 double t3 = dist(p0,b) + dist(a,tar); 27 // if(show) cerr<<t1<<" "<<t2<<" "<<t3<<"ttt"<<endl; 28 return min(t1,min(t2,t3) ); 29 } 30 void work(Point p1,Point p2,Point le,Point ri){ 31 Point testl = le; 32 Point testr = ri; 33 for(int i = 1;i<=100;++i){ 34 Point fml = (ri+le*2)/3; 35 Point fmr = (ri*2 + le)/3; 36 if(f(p1,p2,fml) > f(p1,p2,fmr)){ 37 ri = fmr; 38 } 39 else le = fml; 40 } 41 if(f(p1,p2,le) > ans){ 42 ans = f(p1,p2,le); 43 ans1 = p0; 44 ans2 = le; 45 } 46 } 47 void solve(){ 48 Point pt1 = (Point){xa,ya}; 49 Point pt2 = (Point){xb,yb}; 50 double d1 = dist(p0,pt1); 51 double d2 = dist(p0,pt2); 52 for(int i = 0;i<=11;++i){ 53 work(pt1,pt2,p[i],p[(i+1)%12]); 54 } 55 } 56 int main(){ 57 int T; scanf("%d",&T); 58 for(int cas = 1;cas<=T;++cas){ 59 scanf("%lf %lf %lf %lf %lf %lf",&A,&B,&xa,&ya,&xb,&yb); 60 double mix = min(xa,xb); 61 double miy = min(ya,yb); 62 double mxx = max(xa,xb); 63 double mxy = max(ya,yb); 64 p[0] = (Point){0,0}; 65 p[1] = (Point){mix,0}; 66 p[2] = (Point){mxx,0}; 67 p[3] = (Point){A,0}; 68 p[4] = (Point){A,miy}; 69 p[5] = (Point){A,mxy}; 70 p[6] = (Point){A,B}; 71 p[7] = (Point){mxx,B}; 72 p[8] = (Point){mix,B}; 73 p[9] = (Point){0,B}; 74 p[10] = (Point){0,mxy}; 75 p[11] = (Point){0,miy}; 76 ans = -1; 77 p0 = (Point){0,0}; 78 solve(); 79 p0 = (Point){0,B}; 80 solve(); 81 p0 = (Point){A,B}; 82 solve(); 83 p0 = (Point){A,0}; 84 solve(); 85 printf("Case #%d:\n",cas); 86 printf("%.8f %.8f\n%.8f %.8f\n",ans1.x,ans1.y,ans2.x,ans2.y); 87 // cerr<<ans<<"!"<<endl; 88 } 89 }