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;  
}




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;
}




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;
}




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;  
}





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;
}




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;  
}