2019计蒜客信息学提高组赛前膜你赛 #1(炮击,出行
计蒜客2019CSP比赛第一场
巧妙避开这场比赛……
另外,计蒜客的\(std\)也太毒瘤了
T1炮击
题目描述
\(rize\) 是一个可爱的女孩子。
一天,\(rize\) 进行了炮击的练习。炮击用的靶子为一个圆,其中有 \(n-1\) 个与靶子同心的圆,将靶子划分成了 \(n\) 个区域。这些区域里到外从 \(1\) 到 \(n\) 编号,第 \(i\) 个区域的外径为 \(R_i\) 。每个区域有一个分数,第 \(i\) 个区域的分数为 \(s_i\) 。\(rize\) 发射了 \(m\) 枚炮弹。在靶平面上的以靶心为原点的直角坐标系下,第 \(i\) 枚炮弹击中的区域为一个半径为 \(r_i\)的圆,其圆心的坐标为 \((x_i, y_i)\) 。若一枚炮弹击中的区域与靶子中的某个区域存在交集,则发射这枚炮弹会得到这个区域的分数。这里的区域不包含边界。
\(rize\) 想知道发射每一枚炮弹的得分。
输入格式
第一行两个数 \(n, m\) ;
之后 \(n\) 行,每行两个整数 \(R_i, s_i\);
之后 \(m\) 行,每行三个整数 \(x_i, y_i, r_i\) 。
输出格式
\(m\)行,每行一个数表示答案。
数据范围
有 \(30\%\) 的数据 \(n, m \le 1000\) ;
另有 \(20\%\) 的数据 \(r_i = 1\);
另有 \(20\%\) 的数据 \(s_i = 1\);
对于 \(100\%\) 的数据,\(1 \le n, m \le 2 \times 10^5\),\(1 \le s_i \le 10^4\),\(-10^7 \le x_i, y_i \le 10^7\) ,\(1 \le r_i \le 10^7\) ,\(1 \le R_1 < R_2 < \cdots < R_n \le 10^7\)
“这是一道大水题”
我当时这么说的,但是直到最后我这个蒟蒻还是没写出来……
二分查找\(+\)前缀和
我们可以发现,对于每发炮弹,我们并不需要计算圆的大小,真正需要算的只有它的圆心和靶心的连线的距离分别加减它的半径:\(\sqrt{x^2 + y^2} + r\)是最远点,最近点需要特判,当\(\sqrt{x^2 + y^2} < r\)时最近点是圆心,否则是\(\sqrt{x^2 + y^2} - r\),然后考虑优化,就可以用前缀和,在考虑如何查找前缀和,就可以想到\(logn\)的二分查找……还挺绕
代码:
#include<bits/stdc++.h>
using namespace std;
#define rint register int
#define lb lower_bound
#define ub upper_bound
int n, m;
int f[300010], c[300010];
inline int read( void ){
int re = 0, f = 1;char ch = getchar();
while( ch > '9' || ch < '0' ){
if( ch == '-' ) f = -1;
ch = getchar();
}
while( ch >= '0' && ch <= '9' ){
re = re * 10 + ch - '0';
ch = getchar();
}
return re * f;
}
int main( void ){
n = read(); m = read();
for( rint i = 1; i <= n; i++ ){
c[i] = read();
f[i] = read() + f[i - 1];
}
for( rint i = 1; i <= m; i++ ){
double x, y;
int r;
x = read(); y = read(); r = read();
double dis = sqrt( x * x + y * y );
printf("%d\n",f[lb(c,c+n,(int)ceil(dis)+r)-c]-(dis<=r?0:f[ub(c+1,c+n+1, (int)dis-r)-c-1]));
}
return 0;
}
T2出行
还行,想出了正解,没想到处理方法
题目描述
\(syaro\) 是一个可爱的女孩子。
\(syaro\) 所在的城市有 \(n\)个街区,街区之间共有 \(m\) 条双向通行的道路,且任意两个街区可以通过这些道路互相到达。
一天,\(syaro\) 要带着无数只兔子从 \(1\) 号街区走到 \(n\) 号街区。由于 \(syaro\) 无法管理太多的兔子,于是她决定,如果她经过的道路中存在长度为 \(w\) 的道路,那么她出发时只会带不大于 \(w\) 只兔子。为了节省时间,她只会在总长度最短的若干条路径中选择一条走。
另外,有 \(k\) 个街区由于开了很多咖啡店,整个街区都弥漫着咖啡的气味。由于 \(syaro\) 闻到咖啡的气味就会迷失方向,因此她只会从不经过任何一个这样的街区的最短路径中选择一条走。如果所有最短路径都会经过这样的街区,她就会放弃出行。
\(syaro\) 告诉了 \(cocoa\) 自己的出行计划。 \(cocoa\) 作为一个擅长算术的女孩子,想知道 \(syaro\) 最多能带多少只兔子。当然,放弃出行意味着最多能带 \(0\) 只兔子。
输入格式
第一行三个数 \(n, m, k\) ;
第二行 \(k\)个数,表示开了很多咖啡店的街区编号;
之后$ m$ 行,每行三个数 \(u, v, w\),表示有一条从 \(u\) 到 \(v\) 的长度为 \(w\) 的道路。
输出格式
输出一行一个数表示答案。
数据范围
对于 \(10\%\) 的数据 \(n, m \le 50\), \(m-n \le 10\);
对于 \(30\%\) 的数据 \(n, m \le 10^3\)
;
对于 \(60\%\)的数据 \(n, m \le 10^5\)
;
另有 \(20\%\)的数据 \(k = 0\) ;
对于 \(100\%\) 的数据 \(1 \le n, m \le 10^6\)
,\(0 \le k \le n0≤k≤n,0 \le w \cdot n \le 10^9\)
。不保证没有重边,保证没有自环。
很明显这题是要把有咖啡店的点标记然后不向它们连边
然后在新图上找一下最短路里的最大边
当时不知道怎么想的,跑了个二分答案,二分边的长度,其实正确性有所保证,但是每次二分要跑一个\(O(nlogn)\)的\(dij\),\(1e6\)跑一个\(O(nlogn)\)没有问题的,于是我理所应当的忽略了前面那个二分答案的\(O(logn)\),单单一个\(O(logn)\)不大,但是\(O(logn^2)\)就得考虑一下了,再加上计蒜客的评测姬跑得不快,导致我几乎没拿分
标答应该是\(dp\)
我佛了这代码我码了整整一天,看来还是我太菜了
我刚开始想的是正反向跑最短路然后判哪些点在最短路上,然后就卡了思路,写了一整天
哎,然后看标程发现只跑一遍,\(O(nlogn)\)就过得去,因为我给忘了,最短路上的每个点到起点与终点的距离都是这个点的最短路……然后就可以一边跑一边更新\(dp\)状
态
\(dp\)策略:
对于最短路径,我们可以发现所有的最短路径构成了一个\(DAG\),为啥呢,假如有个环,那不得有条路往回走吗,那么这条路的意义何在呢。
然后我们可以在这个\(DAG\)上跑\(dp\)
不难发现到某个点\(i\)的\(dp[i]\)是这个点之前的所有路径的是最小值,那我们就可以得出这么一个套路性的式子\(dp[i] = max( dp[i], min( dp[fa], w ) )\)
然后需要注意的是,当你找到一个不会更新当前该节点最短路的节点时,你应该直接套用以上式子求值,但是一旦该节点的最短路被更新,就说明之前推出的所有状态作废,就应该重新给\(dp[i]\)赋值为\(0\),再做更新。
另外,给\(dp[1]\)赋初值为\(0x3f3f3f3f\)是这么来的:
你刚开始第一步一定是用\(dp[1]\)去更新其他节点的,然后再用每条边更新\(dp\)值,而用\(dp[1]\)更新时求最小值,所以要赋极大值,同理,加入要求的是最大值,那就应该赋极小值
#include<bits/stdc++.h>
using namespace std;
#define rint register int
int n, m, k, cnt, dismin;
int head[1000010], dis[1000010], dp[1000010], cof[1000010];
bool bok[1000010], vis[1000010];
priority_queue< pair< int, int >, vector< pair< int, int > >, greater< pair< int, int > > > que;
struct node{
int nxt, to, val;
}a[2000010];
inline int read( void ){
int re = 0, f = 1; char ch = getchar();
while( ch > '9' || ch < '0' ){
if( ch == '-' ) f = -1;
ch = getchar();
}
while( ch >= '0' && ch <= '9' ){
re = re * 10 + ch - '0';
ch = getchar();
}
return re * f;
}
inline void addedge( int x, int y, int z ){
a[++cnt].nxt = head[x];
a[cnt].to = y;
a[cnt].val = z;
head[x] = cnt;
}
int main( void ){
n = read(); m = read(); k = read();
for( rint i = 1; i <= k; i++ ) cof[read()] = 1;
for( rint i = 1; i <= m; i++ ){
int u, v, w; u = read(); v = read(); w = read();
if( cof[u] || cof[v] ) continue ;
addedge( u, v, w ); addedge( v, u, w );
}
if( cof[1] || cof[n] ){
cout << 0;
return 0;
}
memset( dis, 0x3f, sizeof( dis ) );
dis[1] = 0;
dp[1] = 0x3f3f3f3f;
que.push( make_pair( 0, 1 ) );
while( que.size() ){
int x = que.top().second;
que.pop();
if( vis[x] ) continue;
vis[x] = 1;
for( rint i = head[x]; i; i = a[i].nxt ){
int v = a[i].to, w = a[i].val;
if( dis[v] < dis[x] + w ) continue ;
if( dis[v] > dis[x] + w ){
dp[v] = 0;
dis[v] = dis[x] + w;
que.push( make_pair( dis[v], v ) );
}
dp[v] = max( dp[v], min( dp[x], w ) );
}
}
cout << dp[n];
return 0;
}
T3会和
目前只能讲讲思路,我这种蒟蒻应该是实现不了代码的……
首先我们可以发现这题是和\(lca\)有关的(逃
\(m = 1\)的情况就是求裸\(lca\),树剖即可
之后的其它点,我们发现最后回合的路径一定在\(lca\)到根节点的路径上,这意味着我们必须求\(lca\)
然后我们还得找在一个点到根节点的路径上同种颜色出现的下一个位置,这个东西显然符合单调栈(宁愿意的话或许可以可持久化线段树?
然后我发现这个题型已经出现过多次了,每次我都做不出,所以我现在去学了,争取早日\(update\)这篇博客
我真的不想\(AFO\)
while( CSP_2019 ){
rp++;
}
\(update_1\) in \(2019.11.8\)