The 2018 ACM-ICPC Chinese Collegiate Programming Contest(Ningxia) 部分题解即AC代码

The 2018 ACM-ICPC Chinese Collegiate Programming Contest(Ningxia)

A - Maximum Element In A Stack

题意:给出栈的一种进栈和出栈的规则,问栈中的最大值

题解:每次进栈的时候将进栈的值与栈中的最大值比较,将较大的值压入栈

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e6 + 56;
typedef long long ll;
int n, p, q, m;
unsigned int SA, SB, SC;
stack<ll> s;
ll ans = 0;
unsigned int rng61(){
    SA ^= SA << 16;
    SA ^= SA >> 5;
    SA ^= SA << 1;
    unsigned int t = SA;
    SA = SB;
    SB = SC;
    SC ^= t ^ SA;
    return SC;
}

void gen(){
    scanf("%d%d%d%d%u%u%u", &n, &p, &q, &m, &SA, &SB, &SC);
    for(int i = 1; i <= n; i++){
        if(rng61() % (p + q) < p){
            //PUSH(rng61() % m + 1);
            ll x = rng61() % m + 1;
            //cout << x << endl;
            if (!s.empty()) x = max(x, s.top());
            s.push(x);
        }
        else{
            if (!s.empty()) s.pop();
            else continue;
        }
        if (!s.empty())ans ^= i * s.top();
    }

}

int main()
{
    int T;
    cin >> T;
    for (int t = 1; t <= T; t++){
        ans = 0;
        while (!s.empty()) s.pop();
        printf("Case #%d: ", t);
        gen();
        cout << ans << '\n';
    }
    return 0;
}

B - Rolling The Polygon

 题意:给一个凸多边形,问多边形旋转一周的过程中,凸多边形中的一点Q经过的路径的长度

题解:计算几何

#include <bits/stdc++.h>
#define sqr(x) ((x)*(x))
using namespace std;
const int maxn = 100;

struct Point{
    double x, y;
}p[maxn], Q;

struct Vec{
    double x, y;
    Vec(){}
    Vec (double a, double b){
        x = a, y = b;
    }
    double operator*(Vec a){
        return a.x*x+a.y*y;
    }
}v[maxn];
const Vec X = {1, 0};
double dis(Point a, Point b){
    return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));
}

double len(Vec a){
    return sqrt(sqr(a.x) + sqr(a.y));
}

double ang(Vec a, Vec b){
    return acos((a * b) / len(a) / len(b));
}
Vec Mns(Point a, Point b){
    return Vec(a.x - b.x, a.y - b.y);
}

int n;
int main()
{
    int T;
    cin >> T;
    for (int t = 1; t <=  T; t++){

        printf("Case #%d: ", t);
        scanf("%d", &n);
        for (int i = 0; i < n; i++){
            scanf("%lf%lf", &p[i].x, &p[i].y);
        }
        p[n] = p[0]; p[n+1] = p[1];
        int sz= 0;
        for (int i = 1; i <= n; i++){
            v[i] = Mns(p[i+1], p[i]);
        }
        v[0] = v[n];
        scanf("%lf%lf", &Q.x, &Q.y);
        double ans = 0;
        for (int i = 1; i <= n; i++){
            double sita = ang(v[i], v[i-1]);
            double l = dis(Q, p[i]);
            ans += l * sita;
        }
       // cout << ans << endl;
        printf("%.3f\n", ans);
    }
    return 0;
}

C - Caesar Cipher

题意:给出原码与密码的变换规则,要用给定的规则将给定的密码还原,当然加密规则特别简单

题解:水题

#include<bits/stdc++.h>

using namespace std;
const int maxn = 100;
char p[maxn],c[maxn],s[maxn];
int n, m;
int main()
{
    int T;
    cin>>T;
    for(int t = 1; t <= T; t++)
    {
        printf("Case #%d: ", t);
        cin >> n >> m;
        cin >> p >> c >> s;
        int key=(int)p[0]-(int)c[0];
        for(int i=0; i<m; i++){
            if(s[i]>='a'&&c[i]<='z'){
                if(s[i]+key>'z')
                    s[i]=(s[i]-26+key);
                else if(s[i]+key<'a')
                    s[i]=s[i]+key+26;
                else
                    s[i]=s[i]+key;
            }
            else
            {
                if(s[i]+key>'Z')
                    s[i]=(s[i]-26+key);
                else if(s[i]+key<'A')
                    s[i]=s[i]+key+26;
                else
                    s[i]=s[i]+key;
            }
        }
        printf("%s\n",s);

    }
    return 0;
}

D - Take Your Seat

 题意:两个问题,

第一个问题:飞机上n个人,第i个人要坐在第i个位置,但是第一个人忘了自己的位置,所以他就会随机找一个位置坐下来,被坐的这个人发现自己的位置被坐了就会随机找一个没有被坐的位置坐下,这n个人是按顺序上飞机的,问第n个人坐在第n个位置的概率是多少

第二个问题:如果这n个人不是按顺序上飞机的,第n个人坐在第n个位置的概率是多少

题解:第一问,可以推出f_n = \frac{(1 + f_2 + f_3 +...+f_{n-1})}{n}

化简一下得                 n\cdot f_n=1+f_2+f_3+...+f_{n-1}                       (1)

将n-1带入                  (n-1)\cdot f_{n-1}=1+f_2+f_3+...+f_{n-2}              (2)

将左边一个f_{n-1}移向变成n\cdot f_{n-1}=1+f_2+f_3+...+f_{n-2}+f_{n-1}      (3)

(3)-(1) 得                                        n\cdot f_{n-1} =n \cdot f_{n}

得                                                          f_{n} = f_{n-1}

容易想到                                                f_2 = \frac{1}{2} $ $ $ $ $ $f_1=1

                                                        f_n = f_2 = \frac{1}{2}

第二问:可以推出f_n = \frac{(1 + f_2 + f_3 +...+f_{n-1}+f_{n})}{n}

发现相比第一问就加了\frac{f_{n}}{n}

g_n=\frac{1}{2}+\frac{f_n}{n}=\frac{1}{2}+\frac{1}{2n} = \frac{n+1}{2 \cdot n}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 1e2 + 10;

int main()
{
    int T;
    double ans1 = 0,ans2 = 0;
    scanf("%d", &T);
    for (int t = 1; t <= T; t++){
        printf("Case #%d: ", t);
        double n, m;
        scanf("%lf%lf", &n, &m);
        if (n == 1) ans1 = 1.0;
        else ans1 = 0.5;
        double ans2 = (1.0 + m) / (2.0 * m);
        //cout << ans1 << " " << ans2 << endl;
        printf("%.6f %.6f\n", ans1, ans2);
    }
    return 0;
}

 

E - 2-3-4 Tree

 题意:给出2-3-4树的构造方法,然后输出2-3-4树的前序遍历

题解:抄了人家的模板

#include <bits/stdc++.h>
#define FOR(i,s,t) for(int i=(s);i<=(t);i++)
#define pb push_back
#define mp make_pair

using namespace std;
typedef long long LL;
const int maxn=1e5+5;
int a[maxn];
int cnt,rt;
struct Node
{
    int f;
    vector<int>d,ch;
    void init(int fa,int x)//f是该节点的父亲? x是当前的结点拥有的值?
    {
        f=fa;
        d.clear(),ch.clear();
        d.push_back(x);//当前节点拥有多少的所有的值
    }
}tr[maxn];
void ins(int p,int x)//(1,x)
{
//    cout<<"rt:"<<rt<<endl;
    if(tr[p].d.size()==3)//代表有三个元素?? 也就是当前节点插满了不能再插进元素了
    {
        int f=tr[p].f;//父亲是谁
        vector<int>cpy_d,cpy_ch;
        cpy_d.swap(tr[p].d),cpy_ch.swap(tr[p].ch);//swap ??  这时候cop_d 和cpy_ch拥有了ty[p].d和ty[p].ch的内容 而这两个变为空?
        if(p==rt)//如果p和rt相等??代表已经是根节点了 上面没有结点
        {
            //相当于中间结点往上提 0 1 2三个结点 中间的自然是1  变为其它两个节点的父亲
            tr[rt=++cnt].init(0,cpy_d[1]);//新的结点  父亲变为0 插入d[1]?


            tr[++cnt].init(rt,cpy_d[0]);//新的结点 父亲变为rt 插入d[0]? 相当于成为左儿子
            tr[p].init(rt,cpy_d[2]);//原来的结点父亲是rt 只剩下d[2]? 相当于成为右儿子
            tr[rt].ch.push_back(cnt),tr[rt].ch.push_back(p);//把这两个结点加入到他们的父亲节点的孩子中
            if(cpy_ch.size())//如果当前节点有孩子 自然孩子也需要进行操作
            {
                //左边两个分到左边 右边两个分到右边  大概是为了平衡吧。。。
                tr[cnt].ch.push_back(cpy_ch[0]),tr[cpy_ch[0]].f=cnt;//原来的左儿子自然也就成为现在左儿子的左儿子了
                tr[cnt].ch.push_back(cpy_ch[1]),tr[cpy_ch[1]].f=cnt;//为什么把1也归为左儿子的儿子?
                tr[p].ch.push_back(cpy_ch[2]),tr[cpy_ch[2]].f=p;//为什么把2 3归为右儿子的儿子?
                tr[p].ch.push_back(cpy_ch[3]),tr[cpy_ch[3]].f=p;
//                cout<<cpy_ch[0]<<" "<<cpy_ch[1]<<" "<<cpy_ch[2]<<" "<<cpy_ch[3]<<endl;
            }
            p=rt;//现在的根节点已经变为rt了
        }
        else//不是根节点  上面有节点
        {
            tr[p].init(f,cpy_d[0]);//左边结点
            tr[++cnt].init(f,cpy_d[2]);//新建右边结点
            tr[f].d.push_back(cpy_d[1]);//把中间结点提上去
            sort(tr[f].d.begin(),tr[f].d.end());//提上元素之后显然要排序
            tr[f].ch.push_back(cnt);//多加了个孩子
            for(int i=tr[f].ch.size()-1;i>1;i--)//??为什么要交换? 懂了 因为你要保证孩子也是有序的呀 所以要把新插入的结点换到p之后
            {
                if(tr[f].ch[i-1]!=p) swap(tr[f].ch[i-1],tr[f].ch[i]);
                else break;
            }
            if(cpy_ch.size())
            {
                tr[p].ch.push_back(cpy_ch[0]),tr[cpy_ch[0]].f=p;
                tr[p].ch.push_back(cpy_ch[1]),tr[cpy_ch[1]].f=p;
                tr[cnt].ch.push_back((cpy_ch[2])),tr[cpy_ch[2]].f=cnt;
                tr[cnt].ch.push_back(cpy_ch[3]),tr[cpy_ch[3]].f=cnt;
            }
            p=f;
        }
    }

//    cout<<"*"<<tr[p].d.size()<<endl;
    //保证当前节点只有两个元素??错的 会有三个
    if(tr[p].ch.size()==0)//没有孩子?? 也就不需要在孩子中进行比较和插入了 直接放进去即可
    {
        tr[p].d.push_back(x);//作为本节点的元素
        sort(tr[p].d.begin(),tr[p].d.end());//你必须保证里面的有序的 所有每次插入元素都需要排序???

    }
    else//有孩子 插入元素就必须和孩子比较了
    {
        if(x<tr[p].d[0]) ins(tr[p].ch[0],x);//如果x<第一个元素 那么很显然 肯定要插入到做儿子中 (因为元素是有序的)所以递归左儿子
        else if(x>tr[p].d[tr[p].d.size()-1]) ins(tr[p].ch[tr[p].ch.size()-1],x);//如果大于最后一个元素 显然是插到右孩子中的 递归右儿子
        else//不比第一个小 不比最后一个大 则肯定插入到中间
        {
            for(int i=1;i<tr[p].d.size();i++)//遍历中间元素
                if(x<tr[p].d[i]) {ins(tr[p].ch[i],x);break;}//小于谁就插入到谁的孩子中
        }
    }
}
void dfs(int p)
{
    for(int i=0;i<tr[p].d.size();i++)
        printf("%d%c",tr[p].d[i],i==tr[p].d.size()-1?'\n':' ');
    for(int i=0;i<tr[p].ch.size();i++) dfs(tr[p].ch[i]);
}
int main()
{
    int T;
    cin>>T;
    int ca=1;
    while(T--)
    {
        int N;
        cin>>N;
        for(int i=1;i<=N;i++) cin>>a[i];
        cnt=rt=1,tr[rt].init(0,a[1]); //第一个点的父亲为0 拥有元素a[1]
        for(int i=2;i<=N;i++) ins(rt,a[i]);//rt不是一直是1吗?
        printf("Case #%d:\n", ca++);
        dfs(rt);
    }
    return 0;
}

F - Moving On

 题意:给出一个图,每条边有长度,每个城市有危险值,要求一条最短路,使得经过的城市的危险值都小于w

题解:离散化,弗洛伊德,按危险值排序,按危险值由小到大加入城市,计算最短路

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int maxn=200 + 2;

int r[maxn];
int dis[maxn][maxn][maxn];
int id[maxn];

bool cmp(int i,int j){
    return r[i]<r[j];
}

int main(){
    int T;
    scanf("%d",&T);
    for(int t=1;t<=T;t++){
        printf("Case #%d:\n",t);
        memset(dis,INF,sizeof(dis));
        int N,Q;
        scanf("%d%d",&N,&Q);
        for(int i=1;i<=N;++i){
            id[i]=i;
            scanf("%d",&r[i]);
        }
        sort(id+1,id+N+1,cmp);

        for(int i=1;i<=N;++i)
            for(int j=1;j<=N;++j)
                scanf("%d",&dis[0][i][j]);
        for(int k=1;k<=N;++k){
          int realk=id[k];
          for(int i=1;i<=N;++i)
            for(int j=1;j<=N;++j) {
                dis[k][i][j]=min(dis[k-1][i][j],dis[k-1][i][realk]+dis[k-1][realk][j]);
            }
        }
        for(int q=1;q<=Q;++q){
            int u,v,dan;
            scanf("%d%d%d",&u,&v,&dan);
            int k=0;
            for(int i=1;i<=N;++i)
                if(r[id[i]]<=dan) k=i;
            printf("%d\n",dis[k][u][v]);
        }
    }
    return 0;
}

G - Factories

 题意:给出一颗有权树,每个叶子节点可以建设工厂,问建k个工厂,如何安排使得所有工厂的之间的距离之和最短

题解:树型dp

f_{ij}表示以i为根节点的子树,建设j个工厂的最小距离

#include <bits/stdc++.h>
#define FOR(i,s,t) for(int i=(s);i<=(t);i++)
#define pb push_back
#define mp make_pair
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 2;
const int maxk = 100 + 2;
const ll inf = 1e12;
const ll INF = 0x3f3f3f3f3f3f3f3f;


vector <pair<int, ll> >  edge[maxn];

ll dp[maxn][maxk];
int sz[maxn];
int n, k, rt;

ll miner(ll a, ll b){
	return a < b ? a : b;
}

void dfs(int u, int fa){
	if (edge[u].size() == 1){
		sz[u] = 1;
		dp[u][1] = 0ll;
	}
	dp[u][0] = 0ll;
	FOR(i, 0, edge[u].size() - 1){
		int v = edge[u][i].first;
		ll w = edge[u][i].second;
		if (v == fa) continue;
		dfs(v, u);
		sz[u] += sz[v];
		dp[u][k] = miner(dp[u][k], dp[v][k]);
		for (int j = min(k, sz[u]); j >= 1; j--){
			FOR(l, 1, min(j, sz[v])){
				dp[u][j] = miner(dp[u][j], dp[u][j - l] + dp[v][l] +1ll * (ll)w *(ll)l * (ll)(1ll * k - 1ll * l));
			}
		}		
	}
}

int main() {
	//freopen("G.in", "r", stdin);
	//freopen("G.out", "w", stdout);
        int T;cin >> T;
        FOR(cas, 1, T){
                printf("Case #%d: ", cas);
                scanf("%d%d",&n, &k);
                FOR(i, 1, n) FOR(j, 0, k) dp[i][j] =inf;
                FOR(i, 1, n) edge[i].clear();
                FOR(i, 1, n) dp[i][0] = 0ll;
                FOR(i, 1, n) sz[i] = 0;
		FOR(i, 1, n - 1){
                        int u, v;
			ll w;
                        scanf("%d%d%lld", &u, &v, &w);
			//printf("%d\n",w);
                        edge[u].pb(mp(v, w));
                        edge[v].pb(mp(u, w));
                }
		rt = 1;
                FOR(i, 1, n){
                        if (edge[i].size() > 1){
                                rt = i;
                                break; 
                        }
                }
                dfs(rt, -1);
		printf("%lld\n", dp[rt][k]);
	}
	return 0;
}

H - Fight Against Monsters

 题意:打怪兽,每个怪兽有生命值和攻击力,每个怪兽在没死之前单位时间都会攻击玩家,玩家每打一个怪兽伤害都是从1开始递增,问玩家最少需要承受多少伤害

题解:贪心(排序)

\frac{Hp}{Turn}排序,Turn是需要打几回合怪兽才会死的回合数

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e5 + 5;
typedef long long ll;
struct Monster{
    int hp, atk;
}mons[maxn];

int n;

bool cmp(Monster A, Monster B){
    ll x1 = ceil((sqrt(1.0 + 8.0 * A.hp) - 1) / 2.0);
    ll x2 = ceil((sqrt(1.0 + 8.0 * B.hp) - 1) / 2.0);
    if (1ll * A.atk * x2 == 1ll * x1 * B.atk) return A.atk > B.atk;
    return 1ll * A.atk * x2 > 1ll * x1 * B.atk;
}

ll sum[maxn];

int main()
{
    int T;
    cin >> T;
    for (int t = 1; t <= T; t++){
        printf("Case #%d: ", t);
        scanf("%d", &n);
        for (int i = 0; i < n; i++){
            scanf("%d%d",&mons[i].hp, &mons[i].atk);
        }
        sort(mons, mons+n, cmp);
        ll turn = 0ll, ans = 0ll;
        for (int i = 0; i <= n + 1; i++) sum[i] = 0ll;
        for (int i = n - 1 ; i >= 0; i--){
            sum[i] += sum[i + 1] + (ll)mons[i].atk;
        }
        int i = 0;
        for (i = 0; i < n; i++){
            turn = (ll)ceil((sqrt(1.0 + 8.0 * mons[i].hp) - 1) / 2.0);
            ans += 1ll* sum[i] * (ll)turn;
           // cout << turn << endl;
        }
        cout << ans <<'\n';
    }
    return 0;
}

J - Nested Triangles

 题意:新定义之中东西,Nested Triangles,就是若干个公用一条边的三角形,除了公用的这条边另外的两条边都不相交或者重合,问最大的Nested Triangles最多有几个三角形

题解:几何+最长上升子序列

按照其中一边的边排序,另一边的边最最长上升子序列

#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define FOR(i,s,t) for(int i=(s);i<=(t);i++)

using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int maxn = 1e5 + 5;

struct Vec{
	ll x, y;
	Vec(){}
	Vec(ll a, ll b){x = a,y = b;}
}PQ, QP; 

struct Node{
	ll x, y;
	Vec operator -(const Node A)const{return Vec(x - A.x, y - A.y);}
}node[maxn],P, Q;


vector<int> id[2];
int pre[maxn], f[maxn], c[maxn];
int n;

ll Cross(Vec A, Vec B){return A.x * B.y - A.y * B.x;}
ll Mult(Vec A, Vec B){return A.x * B.x + A.y * B.y;}
bool isUp(Node x){return Cross(PQ, x - P) > 0;}

int lowbit(int x) {return x&(-x);}

void modify(int x, int val){
	for (int i = x; i < maxn; i+= lowbit(i)){
		if (x != -1 && (c[i] == -1 || f[val] >= f[c[i]])){
			if (c[i] == -1 || f[val] > f[c[i]]) c[i] = val;
			//else if (f[x] == f[c[i]] && x < c[i]) c[i] = x;
			else if (val < c[i]) c[i] = val;
		}
	}
	return ;
}

int query(int x){
	int ret = -1;
	for (int i = x; i>0; i-=lowbit(i)){
		if (c[i] != -1 && (ret == -1 || f[c[i]] >= f[ret])){
			if (ret == - 1|| f[c[i]] > f[ret]) ret = c[i];
			//else if (f[c[i]] == f[ret] && c[i] < ret) ret = c[i]; 
			else if (c[i] < ret) ret = c[i] ;
		}
	}
	return ret;
}

bool cmp1(int i, int j){return Cross(node[i] - P, node[j] - P) > 0;}
bool cmp2(int i, int j){return Cross(node[i] - Q, node[j] - Q) < 0;} 

int a[maxn], b[maxn];

void print(int x){
	int i = x;
	while (i != -1){
		printf("%d\n", i);
		i = pre[i];
	}
}

int main(){
	int T;cin >> T;
	FOR(cas, 1, T){
		scanf("%lld%lld%lld%lld", &P.x, &P.y, &Q.x, &Q.y);
		PQ = Q - P;
		QP = P - Q;
		id[0].clear();
		id[1].clear();
		scanf("%d", &n);
		FOR(i , 1, n){
			scanf("%lld%lld",&node[i].x, &node[i].y);
			if (isUp(node[i])) id[0].pb(i);
			else id[1].pb(i);
		}
		int M[2] = {0};
		FOR(i, 0, 1){
			sort(id[i].begin(), id[i].end(), cmp1);
		
			for (int j =  0; j < id[i].size(); j++){
				if (j == 0) a[id[i][j]] = 1;
				else if (cmp1(id[i][j - 1], id[i][j])) a[id[i][j]] = a[id[i][j-1]] + 1;
				else a[id[i][j]] = a[id[i][j-1]]; 
			}
			sort(id[i].begin(), id[i].end(), cmp2);
			for (int j = 0; j < id[i].size(); j++){
				if (j == 0) b[id[i][j]] = 1;
				else if (cmp2(id[i][j - 1], id[i][j])) b[id[i][j]] = b[id[i][j-1]]+1;
				else b[id[i][j]] = b[id[i][j-1]];
			}
			for (int j = 0; j <= id[i].size() ; j++) c[j] = -1;
			for (int j = 0; j < id[i].size() ; j++){
				int k = j;
				while (k + 1 < id[i].size()&& b[id[i][k]] == b[id[i][k + 1]]) k++;
				FOR(p, j , k){
					int o = id[i][p];
					pre[o] = query(a[o] - 1);
					f[o] = pre[o] == -1 ? 1 : f[pre[o]] + 1;
					M[i] = max(M[i], f[o]);
				}
				FOR(p, j ,k){
					int o = id[i][p];
					modify(a[o], o);
				}
				j = k;
			}
			swap(P, Q);
			swap(PQ, QP);
		}
		//FOR(j, 1, n) cout << c[j] << " " ;cout << endl;
		
		printf("Case #%d: %d\n", cas, max(M[1], M[0]));
		FOR(j, 1, n){
			if (f[j] == max(M[1], M[0])){
				print(j);
				break;
			}
		}
	}
	return 0;
}

L - Continuous Intervals

 题意:给出一串数,定义一段区间是连续的表示这段区间排序以后相邻两个数的差不超过1

题解:用MAX表示这段区间的最大值,MIN表示这段区间的最小值,CNT表示这段区间的不同数的种类数;MAX-MIN=CNT-1时,满足条件,MAX-MIN>=CNT-1恒成立。简单化简一下就是一段区间的MAX-MIN-CNT+1=0,则这个区间符合条件。

枚举区间的R,计算有多少个L满足L,R这个区间满足条件,用线段树保存MAX-MIN-CNT-1的最小值及其数量,当枚举一个新的R时,最大值受到影响的区间就是右边第一个比a[L]大的数是a[R]的L,可以用单调栈维护,同理最小值也可以用单调栈维护。CNT受影响的区间就是a[R]上一次出现的位置到R这段区间的L,这段区间的每一个L,构成的区间[L,R]都多了一个新的数,所以CNT多了1,所以MAX-MIN-CNT+1,就少1。可以用map维护上一个a[R]出现的位置,也可以离散化。

#include <bits/stdc++.h>
#define FOR(I,S,T) for(int I=(S);I<=(T);I++)
#define pb push_back
#define mp make_pair
#define fi first
#define se second

using namespace std;

typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int maxn = 1e5 +10;

int M[maxn << 2], cnt[maxn << 2], tag[maxn << 2];

void gather(int p) {
    M[p] = min(M[p << 1], M[p << 1 | 1]);
    if (M[p << 1] == M[p << 1 | 1]) cnt[p] = cnt[p << 1] + cnt[p << 1 | 1];
    else if (M[p << 1] < M[p << 1 | 1]) cnt[p] = cnt[p << 1];
    else cnt[p] = cnt[p << 1 | 1];
}

void push(int p){
    if (tag[p]){
        tag[p << 1] += tag[p];
        tag[p << 1 | 1] += tag[p];
        M[p << 1] += tag[p];
        M[p << 1 | 1] += tag[p];
        tag[p] = 0;
    }
}

void build(int p, int l, int r){
    tag[p] = 0, cnt[p] = 1;
    if (l < r){
        int mid = (l + r) / 2;
        build(p << 1, l, mid);
        build(p << 1 | 1, mid + 1, r);
        gather(p);
    }
    else M[p] = 0;
}

void modify(int p, int tl, int tr, int l, int r, int v){
    if (tl > tr) return ;
    if (tl > r || tr < l) return ;
    if (l <= tl && tr <= r){
        tag[p] += v;
        M[p] += v;
        return;
    }
    push(p);
    int mid = (tl + tr) >> 1;
    modify(p << 1, tl, mid, l, r, v);
    modify(p << 1 | 1, mid + 1, tr, l, r, v);
    gather(p);
}

int query(int p, int tl, int tr, int l, int r){
    if (tl > tr) return 0;
    if (tr < l || tl > r) return 0;
    if (l <= tl && tr <= r) return M[p] == 0 ? cnt[p] : 0;
    push(p);
    int mid = (tl + tr) >> 1;
    return query(p << 1, tl, mid, l, r) + query(p << 1 | 1, mid + 1, tr, l, r);
}

int a[maxn];
map<int, int> pre;
stack<int> up, down;

int main() {
    int T;
    cin >> T;
    FOR(cas, 1, T){
        int n;
        ll ans = 0;
        scanf("%d", &n);
        build(1, 1, n);
        while (!up.empty()) up.pop();
        while (!down.empty()) down.pop();
        pre.clear();
        FOR(i,1,n) {
            scanf("%d", a + i);

            while (!up.empty() && a[up.top()] > a[i]) {
                int o = up.top();
                up.pop();
                if (!up.empty()) modify(1, 1, n, up.top() + 1, o, a[o] - a[i]);
                else modify(1, 1, n, 1, o, a[o] - a[i]);
            }
            up.push(i);
            while (!down.empty() && a[down.top()] < a[i]) {
                int o = down.top();
                down.pop();
                if (!down.empty()) modify(1, 1, n, down.top() + 1, o, a[i] - a[o]);
                else modify(1, 1, n, 1, o, a[i] - a[o]);
            }
            down.push(i);

            if (pre.find(a[i]) != pre.end()) modify(1, 1, n, pre[a[i]] + 1, i - 1, -1);
            else modify(1, 1, n, 1, i - 1, -1);
            pre[a[i]] = i;
            ans += query(1, 1, n, 1, i);
        }
        printf("Case #%d: %lld\n", cas, ans);
    }
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ACM-ICPC(国际大学生程序设计竞赛)是一项面向大学生的计算机编程竞赛,涉及算法和数据结构等领域。在比赛中,选手需要解决一系列编程问题,使用合适的算法和数据结构来实现正确和高效的解决方案。 对于整理ACM-ICPC模板,以下是一些建议: 1. 了解比赛要求:首先,你需要了解ACM-ICPC比赛的具体要求和规则。这包括了解比赛所涉及的算法和数据结构,以及题目的类型和难度等。 2. 收集资料:收集与ACM-ICPC相关的资料,包括经典算法和数据结构的实现代码、常见问题的解题思路等。可以参考教材、博客、论文等资源。 3. 整理模板:将收集到的资料整理成模板。可以按照算法和数据结构的分类进行整理,例如排序算法、图算法、字符串算法等。对每个模板,添加必要的注释和示例代码,以便理解和使用。 4. 测试代码:对每个模板编写测试代码,确保它们的正确性和可靠性。可以使用已知的测试用例或自行设计测试用例。 5. 更新与扩充:定期更新和扩充模板,以适应ACM-ICPC比赛中新出现的算法和数据结构。同时,根据自己的经验和理解,对模板进行优化和改进。 6. 练习和复习:在比赛之前,利用整理好的模板进行练习和复习。尝试解决一些经典问题,使用模板中的算法和数据结构进行实现,并进行优化。 希望这些建议对你整理ACM-ICPC模板有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值