A Sum of Medians 线段树
一个很巧妙,很有趣的线段树。
题意:
给你3种操作:
1. 向集合中加入数x。
2.在集合中删除数x。
3.求和sum: 将集合中的数组排好序,将下标(从1开始) 对5取模为3 的位置的数 求和。
思路:
线段树结点
sum[i] 表示 对5取模为i 的数的和。
cnt 表示这个区间内存在的数的个数。
那么每次1,2 操作更新单个结点, 然后pushup:
左子树的位置和父结点的下标位置是一样的, 右子树需要加上左子树的个数 才是父结点的下标。 这样更新sum数组即可。
每次查询输出1号结点的sum[3]即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 100000 + 10;
typedef long long LL;
struct node{
LL sum[5];
int cnt;
}nod[maxn<<2];
struct Node{
int op; /// add is 0 , del is 1 , sumi is 2;
int v;
}p[maxn];
char cmd[5];
int a[maxn];
int cnt;
int get(int x){
int l = 0,r = cnt - 1, m;
while(l <= r){
m = l + r >> 1;
if (a[m] == x) return m + 1;
else if (a[m] > x){
r = m -1;
}
else l = m + 1;
}
}
void pushup(int o){
int lson = o << 1;
int rson = o << 1 | 1;
nod[o].cnt = nod[lson].cnt + nod[rson].cnt;
for (int i = 0; i < 5; ++i){
nod[o].sum[i] = nod[lson].sum[i];
}
for (int i = 0; i < 5; ++i){
if (i != 0){
nod[o].sum[(i+nod[lson].cnt)%5 ] += nod[rson].sum[i];
}
else {
nod[o].sum[(nod[lson].cnt+5)%5 ] += nod[rson].sum[i];
}
}
}
void update(int pos,int c,int l,int r,int o){
if (l == r){
if (c == 0){
memset(nod[o].sum,0, sizeof(nod[o].sum));
nod[o].cnt = 0;
}
else {
nod[o].cnt = 1;
memset(nod[o].sum,0, sizeof(nod[o].sum));
nod[o].sum[1] = a[pos-1];
}
return;
}
int m = l + r >> 1;
if (m >= pos){
update(pos,c,l,m,o<<1);
}
else {
update(pos,c,m+1,r,o<<1|1);
}
pushup(o);
}
int main(){
int n;
while(~scanf("%d",&n)){
cnt = 0;
for (int i = 0; i < n; ++i){
scanf("%s", cmd);
if (cmd[0] == 'a'){
p[i].op = 0;
scanf("%d",&p[i].v);
a[cnt++] = p[i].v;
}
else if (cmd[0] == 'd'){
p[i].op = 1;
scanf("%d",&p[i].v);
a[cnt++] = p[i].v;
}
else {
p[i].op = 2;
}
}
sort(a,a+cnt);
cnt = unique(a,a+cnt) - a;
memset(nod,0,sizeof nod);
for (int i = 0; i < n; ++i){
if (p[i].op == 2){
printf("%I64d\n", nod[1].sum[3]);
}
else if (p[i].op == 0){
update(get(p[i].v), 1, 1, n, 1);
}
else {
update(get(p[i].v ), 0, 1, n, 1);
}
}
}
return 0;
}
B
The Frog's Games (二分套二分)
这题我的错, 没有注意到选拔赛上已经出了这个题目。
题意:
青蛙跳河, 告诉你n 个石头 的位置, 告诉你青蛙跳的次数限制, 求青蛙最远跳的距离的最小值。
思路:
最大值最小,显然二分。
直接二分最小值。 然后每次看看用这个力量跳 能否在规定的次数内跳完即可。(这样我的 是 二分套二分。)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 500000 + 10;
int L,n,m;
long long a[maxn];
int get(int x){
int l = 0,r = n,md;
while(l <= r){
md = l + r >> 1;
if (a[md] >= x){
r = md-1;
}
else l = md+1;
}
return r;
}
bool judge(long long x){
int cnt = 0;
long long cur = 0;
int la = 0;
while(cur < L){
cur = a[get(cur+x)];
if (cur == la){
return 0;
}
la = cur;
++cnt;
}
return cnt <= m;
}
int main(){
while(~scanf("%d %d %d",&L, &n, &m)){
a[0] = 0;
for (int i = 1; i <= n; ++i){
int x;
scanf("%d",&x);
a[i] = x;
}
int l = 0,r = 1e9+7;
sort(a,a+n+1);
a[++n] = L;
a[++n] = 0x3f3f3f3f;
while(l <= r){
int md = l +r >> 1;
// printf("%d\n", md);
if (judge(md))r = md - 1;
else l = md + 1;
}
printf("%d\n", r);
}
return 0;
}
C
Fire! (bfs)
题意:
告诉你人的位置, 告诉你火的位置, 火每秒会延伸。 求人走出来最少时间。 出不来输出 IMPOSSIBLE
思路:
两次bfs, 第一次 bfs 预处理所有火的到每个格子的时间。
在一次bfs 搜索人出来的时间, 遇到火不走即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int dx[] = {1,-1,0,0};
const int dy[] = {0,0,-1,1};
const int maxn = 1000 + 10;
const int inf = 0x3f3f3f3f;
char s[maxn][maxn];
int n,m;
int fire[maxn][maxn];
struct Node{
int x,y;
int t;
Node(int x = 0,int y = 0,int t = 0):x(x), y(y), t(t){}
};
queue<Node>q1;
bool vis[maxn][maxn];
void bfs1(){
memset(vis,0,sizeof vis);
while(!q1.empty()){
Node u = q1.front(); q1.pop();
for (int i = 0; i < 4; ++i){
int xx = u.x + dx[i];
int yy = u.y + dy[i];
if (fire[xx][yy] != inf) continue;
if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && s[xx][yy] != '#' && !vis[xx][yy]){
vis[xx][yy] = 1;
fire[xx][yy] = u.t + 1;
q1.push(Node(xx,yy,u.t + 1));
}
}
}
}
int bfs2(){
memset(vis,0,sizeof vis);
int sx,sy;
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= m; ++j){
if (s[i][j] == 'J'){
sx = i;
sy = j;
}
}
}
while(!q1.empty()) q1.pop();
q1.push(Node(sx,sy,0));
vis[sx][sy] = 1;
int ans = inf;
while(!q1.empty()){
Node u = q1.front();q1.pop();
for (int i = 0; i < 4; ++i){
int xx = dx[i] + u.x;
int yy = dy[i] + u.y;
if (s[xx][yy] == 0){
return u.t+1;
}
else {
if (vis[xx][yy])continue;
if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && s[xx][yy] != '#' && fire[xx][yy] > u.t + 1){
q1.push(Node(xx,yy,u.t+1));
vis[xx][yy] = 1;
}
}
}
}
return ans;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d %d",&n, &m);
for (int i = 1; i <= n; ++i){
scanf("%s", s[i] + 1);
}
memset(fire,inf,sizeof fire);
while(!q1.empty()) q1.pop();
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= m; ++j){
if (s[i][j] == 'F'){
q1.push(Node(i,j,0));
}
}
}
bfs1();
int ans = bfs2();
if (ans == inf) puts("IMPOSSIBLE");
else printf("%d\n", ans);
}
return 0;
}
D
hannnnah_j’s Biological Test (组合数)
大体题意:
给你n 个座位,和m 个人, 安排在一个圆桌子上,要求任意两个人之间的座位至少为k 个,求方案数,答案对1e9取模?
思路:
我们先给m 个人放好,然后在每个人后在添加k 个座位,先保证至少k 个座位,然后会剩下 n - m - m*k个座位,在把剩下的座位插到已经存在的座位里。
那么这个问题就可以转换为:
你有n-m-m*k个球,要求放在m个不同的箱子里,有几种放法?
如果有n 个球,放在m 个不同箱子里。
可以这么考虑,在n 个球后 插入m-1个球,然后任意挑选m-1个球作为隔板,那么答案就是C(n+m-1,m-1)
那么这个题的答案就是 C(n-m-m*k+m-1,m-1) *n/m
之所以乘以n 是因为第一个学生有n 种选择,任意挑选一个座位,但是这样的话难免重复,因为1 3 5 和 3 5 1 和 5 1 3 是一样的,在除以m 就是学生重复的!
然后就是组合数递推取模了, 会有除法,直接快速幂逆元即可!
注意:
只有一个学生,这个公式是不成立的,需要单独判断!
m = 1, ans=n;
在一点是 递推组合数时,不能直接从1递推到n,这样会超时,应该从1 算到 m-1。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 1000000 + 10;
ll c[maxn];
ll my_pow(ll a,ll n){
ll ans = 1;
while(n){
if (n & 1)
ans = (ans % mod * a % mod) % mod;
n/=2;
a = (a%mod*a%mod)%mod;
}
return ans;
}
ll C(ll n,ll m){
c[0] = 1;
for (int i = 1; i <= m; ++i)c[i] = (((c[i-1]%mod * (n-i+1)%(mod)) ) * my_pow(i,mod-2)) % mod;
}
ll n, m, k;
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%I64d%I64d%I64d",&n, &m, &k);
if (m == 1){
printf("%d\n",n);
continue;
}
if (n < m + m*k){
printf("0\n");
continue;
}
C(n-m*k-1,m-1);
ll ans = (((n%(mod) * c[m-1] % (mod))%(mod))*my_pow(m,mod-2)) % mod;
printf("%I64d\n",ans);
}
return 0;
}
E
Minimax Triangulation 区间dp(最大面积的最小三角形剖分)
这题本身应是一个炒鸡水的最短路,不知为啥 就成了这个区间dp= =~
题意:
给你一个n 个点的多边形,输入按着沿着边输入(不用排序),要求分成n-2个三角形,使得这n-2个三角形中最大的三角形的面积尽可能小,输出最小面积!
思路:
区间dp思想!
令dp[i][j] 是用第i 个点到第j 个点能组成最大三角形最小面积!
然后枚举i 和j 之间的点作为 第三个点,然后分成两部分 i~k 和k~j 继续寻找!
不过有坑:
需要注意,如果有个点凹进去了,要注意三角形是否合理!
当你用i,j,k 个点构成三角形时,如果存在另一个点l 使得l 在这个三角形内部,那么这个ijk三角形就不合理了!!
计算三角形面积用叉积或者海伦公式都可以!
计算点是否在三角形内部 用叉积就可以了 三个叉积 同号就不合理了!
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 57;
const int inf = 0x3f3f3f3f;
struct Point{
double x,y;
Point(double x = 0,double y = 0):x(x),y(y){}
void read(){
scanf("%lf %lf",&x,&y);
}
}p[maxn];
typedef Point Vector;
double Cross(Vector A, Vector B){ // 叉积
return A.x*B.y - B.x*A.y;
}
Point operator - (const Point A,const Point B){
return Point(A.x - B.x,A.y-B.y);
}
int n;
double dp[maxn][maxn];
bool check(int a,int b,int c){// 检测是否有另外一个点在 abc三角形内部!
for (int i = 0; i < n; ++i){
if (i == a || i == b || i == c)continue;
Vector ab = p[b]-p[a];
Vector bc = p[c]-p[b];
Vector ca = p[a]-p[c];
Vector ai = p[i]-p[a];
Vector bi = p[i]-p[b];
Vector ci = p[i]-p[c];
double t1 = Cross(ab,ai);
double t2 = Cross(bc,bi);
double t3 = Cross(ca,ci);
if (t1 > 0 && t2 > 0 && t3 > 0 || t1 < 0 && t2 < 0 && t3 < 0) return 1; // return 1表示不合理!
}
return 0;
}
double dfs(int i,int j){
double& ans = dp[i][j];
if (ans != -1)return ans;
if (i >= j)return ans = 0;
if (i == j - 1)return ans = 0;
ans = inf;
for (int k = i+1; k < j; ++k){
if (check(i,j,k))continue;
double t1 = dfs(i,k); //分成两部分寻找
double t2 = dfs(k,j);
double t3 = fabs(Cross(p[j]-p[i],p[k]-p[i])/2.0);
double tmp = -inf;
if (t1)tmp = max(tmp,t1);
if (t2)tmp = max(tmp,t2);//获得最大三角形面积
if (t3)tmp = max(tmp,t3);
if (tmp != -inf) ans = min(ans,tmp); //更新最大三角形最小面积!
}
return ans;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for (int i = 0; i < maxn; ++i){
for (int j = 0; j < maxn; ++j)
dp[i][j] = -1;
}
for (int i = 0; i < n; ++i){
p[i].read();
}
printf("%.1f\n",dfs(0,n-1));
}
return 0;
}
F
Halloween Costumes (区间dp)
题意:
告诉你n 天需要穿衣服的类型, 你可以套着穿衣服, 但是如果拖下来,这件就不能用了, 求最少衣服件数。
思路:
区间dp
令dp[i][j] 表示 i~j 区间的最少衣服件数。
首先第j件衣服 可以穿一个新的。 即 dp[i][j] = dp[i][j-1] + 1;
其次第j 件衣服也可以穿前面的。 即 dp[i][j] = min{ dp[i][k] + dp[k+1][j-1],, a[k] == a[j]}
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 100 + 10;
char s[maxn];
int a[maxn];
int dp[maxn][maxn];
int dfs(int l,int r){
int& ans = dp[l][r];
if (ans != -1) return ans;
if (l > r) return ans = 0;
if (l == r) return ans = 1;
ans = dfs(l,r-1) + 1;
for (int i = r - 1; i >= l; --i){
if (a[i] == a[r]){
ans = min(ans, dfs(l,i) + dfs(i+1, r-1));
}
}
return ans;
}
int main(){
int T,ks = 0;
scanf("%d",&T);
while(T--){
int n;
memset(dp,-1,sizeof dp);
scanf("%d",&n);
for (int i = 0; i < n; ++i){
scanf("%d",&a[i]);
}
printf("Case %d: %d\n", ++ks, dfs(0,n-1));
}
return 0;
}