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 }
View Code

 

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 }
View Code

 

E:

问题简述:有n*m的格子,每个格子都有魔法值值,第i行第j列的值为 V[i][j] = X[ (i-1) * m + j ] ,其中Xi=(A×Xi1+B×Xi2+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 }
View Code

 

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 }
View Code

 

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 }
View Code

 

posted @ 2020-03-13 12:31  小布鞋  阅读(122)  评论(0编辑  收藏