2019 ICPC Asia Yinchuan Regional

目录

A. Girls Band Party

大意:

给出n张牌,每张牌都有花色和名字,以及价值,只能选择不同名字的五张牌,有5种奖励名字和一种奖励花色,和奖励名字相同的名字可以提供10%的加成,相同的颜色可以提供20%的加成,这些加成可以累加到最后一起结算,问最大的价值和是多少

思路:

分组背包问题,但是需要加一个维度记录加成的等级,这样最后再计算价值和即可

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;
typedef long long LL;
int t, n,level[N],dp[N][10][20];
struct node{
    string name;
    string color;
    int val;
} a[N];
unordered_map<string, int> mp;
vector<int> g[N];
int main(){
    cin>>t;
    for(int j = 0;j < 6; ++j)
        for(int i = 0;i < 16; ++i)
            dp[0][j][i] = -0x3f3f3f3f;
    while(t--){
        cin>>n;
        mp.clear();
        int idx = 1;
        for (int i = 1; i <= n;i++){
            cin >> a[i].name >> a[i].color >> a[i].val;
            level[i] = 0;
            if(mp[a[i].name]==0){
                g[idx].clear();
                mp[a[i].name] = idx++;
            }
            g[mp[a[i].name]].push_back(i);
        }
        for (int i = 1; i <= 5;i++){
            string name;
            cin >> name;
            for (int j = 1; j <= n;j++){
                if(a[j].name==name){
                    level[j] += 1;
                }
            }
        }
        string color;
        cin >> color;
        for (int i = 1; i <= n;i++){
            if(color==a[i].color){
                level[i] += 2;
            }
        }
        dp[0][0][0] = 0;
        for (int i = 1; i < idx;i++){
            for (int j = 1; j <= 5;j++){
                for (int k = 0; k <= 15;k++){
                    dp[i][j][k] = dp[i - 1][j][k];
                }
            }
            for (int p = 0; p < g[i].size(); p++){
                for (int j = 1; j <= 5; j++){
                    for (int k = level[g[i][p]]; k <= 15; k++){
                        dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - 1][k - level[g[i][p]]] + a[g[i][p]].val);
                    }
                }
            }  
        }
        int res = 0;
        for (int i = 0; i <= 15;i++){
            res = max(res, dp[idx-1][5][i] + dp[idx-1][5][i] * i / 10);
        }
        cout << res << endl;
    }
    return 0;
}

B. So Easy

大意:

初始是一个全为0的数组,每次操作都是将一列或者一行全加1,最后选择一个数将其置为-1,现在给出最后的结果,问-1的位置在置为-1前是多少

思路:

因为数据量很小,直接暴力每一行和每一列,都减去该列(行)最小的数字,最后看-1的位置是多少

#include<bits/stdc++.h>

using namespace std;

const int N = 1e3 + 5;
int n,mp[N][N],x,y;
int main(){
    cin >> n;
    for (int i = 0; i < n;i++){
        for (int j = 0; j < n;j++){
            cin >> mp[i][j];
            if(mp[i][j]==-1){
                x = i;
                y = j;
            }
        }
    }
    for (int i = 0; i < n;i++){
        int minn = 0x3f3f3f3f;
        for (int j = 0; j < n;j++){
            if(minn>mp[i][j]&&(!(i==x&&j==y))){
                minn = mp[i][j];
            }
        }
        for (int j = 0; j < n;j++){
            mp[i][j] -= minn;
        }
    }
    for (int j = 0; j < n;j++){
        int minn = 0x3f3f3f3f;
        for (int i = 0; i < n;i++){
            if(minn>mp[i][j]&&(!(i==x&&j==y))){
                minn = mp[i][j];
            }
        }
        for (int i = 0; i < n;i++){
            mp[i][j] -= minn;
        }
    }
    cout << -(mp[x][y] + 1) << endl;
    return 0;
}

C. Image Processing

咕咕咕

D. Easy Problem

咕咕咕

E. XOR Tree

咕咕咕

F. Function!

大意:

2019 ICPC Asia Yinchuan Regional

(图源:https://blog.csdn.net/FSAHFGSADHSAKNDAS/article/details/106162905)

思路:

当\(a<=\sqrt{n}\)时直接跳着做,否则直接O(1)的复杂度求出解

需要注意的是,因为n很大,当他去乘别的数的时候要先取模再乘,不能乘完再取模,否则就wa了....

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
typedef long long LL;
LL n, res, inv2, inv6;
const LL mod = 998244353;

LL qmi(LL a, LL k, LL p)
{
    LL res = 1 % p; // res记录答案, 模上p是为了防止k为0,p为1的特殊情况
    while (k)
    { // 只要还有剩下位数
        if (k & 1)
            res = (LL)res * a % p; // 判断最后一位是否为1,如果为1就乘上a,模上p, 乘法时可能爆int,所以变成long long
        k >>= 1;                   // 右移一位
        a = (LL)a * a % p;         // 当前a等于上一次的a平方,取模,平方时可能爆int,所以变成long long
    }
    return res;
}

LL s1(LL k)
{
    k = k % mod;
    return k * (k + 1) % mod * inv2 % mod;
}

LL s2(LL k)
{ //平方和
    return k %= mod, k * (k + 1) % mod * (k * 2 + 1) % mod * inv6 % mod;
}

int main()
{
    cin >> n;
    inv2 = qmi(2, mod - 2, mod), inv6 = qmi(6, mod - 2, mod);
    LL a;
    for (a = 2; a * a <= n; a++)
    {
        LL sum = a - 1;
        LL pre = a, now = a * a;
        LL t=1;
        for (LL i = 2; i <=  n; i++)
        {
            
            if (sum + now - pre > n)
            {
                res = (res + a * (n - sum) % mod * (i - 1) % mod) % mod;
                break;
            }
            res = (res + a * (i - 1) % mod * (now - pre) % mod) % mod;
            sum += now - pre;
            pre = now;
            now = now * a;
        }
    }
    res = (res + ((n + 1) % mod * (s1(n) - s1(a - 1) + mod) % mod) - (s2(n) - s2(a - 1) + mod) % mod + mod) % mod;                                             
    cout << res << endl;
    return 0;
}

G. Pot!!

大意:

一个数组初始均为1,有两个操作,一个是修改:将区间\([l,r]\)的值乘上\(x,(1<=x<=10)\)

另一个是查询:查询区间\([l,r]\)上的数,能被2,3,5,7整除次数的最大值。

思路:

直接线段树维护被2 3 5 7 整除的次数最大值以及全部的最大值即可

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;
typedef long long LL;

struct node{
    int sum2, sum3, sum5, sum7, maxn,lazy2,lazy3,lazy5,lazy7;
}dat[N << 2];
int a[N], n, m,prime[4]={2,3,5,7};

// 上传标记,每次左右子树建树/区间修改完都需要上传
void pushup(int rt) {
    dat[rt].sum2 = max(dat[rt << 1].sum2, dat[rt << 1 | 1].sum2);
    dat[rt].sum3 = max(dat[rt << 1].sum3, dat[rt << 1 | 1].sum3);
    dat[rt].sum5 = max(dat[rt << 1].sum5, dat[rt << 1 | 1].sum5);
    dat[rt].sum7 = max(dat[rt << 1].sum7, dat[rt << 1 | 1].sum7);
    dat[rt].maxn = max(max(dat[rt].sum2, dat[rt].sum3), max(dat[rt].sum5, dat[rt].sum7));
}

// 建树
void build(int rt, int l, int r) {
    if (l == r) {  // 递归到叶节点
        dat[rt].sum2 = dat[rt].sum3 = dat[rt].sum5 = dat[rt].sum7 = 0;
        dat[rt].lazy2 = dat[rt].lazy3 = dat[rt].lazy5 = dat[rt].lazy7 = 0;
        dat[rt].maxn = 0;
        return;
    }
    // 递归建立左右子树
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);  // 上传
}

// 下传,下传标记,同时改变dat数组
void pushdown(int rt, int l, int r) {
    if (dat[rt].lazy2) {
        dat[rt << 1].lazy2 += dat[rt].lazy2;
        dat[rt << 1 | 1].lazy2 += dat[rt].lazy2;
        dat[rt << 1].sum2 += dat[rt].lazy2;
        dat[rt << 1 | 1].sum2 += dat[rt].lazy2;
        dat[rt].lazy2 = 0;
    }
    if (dat[rt].lazy3) {
        dat[rt << 1].lazy3 += dat[rt].lazy3;
        dat[rt << 1 | 1].lazy3 += dat[rt].lazy3;
        dat[rt << 1].sum3 += dat[rt].lazy3;
        dat[rt << 1 | 1].sum3 += dat[rt].lazy3;
        dat[rt].lazy3 = 0;
    }
    if (dat[rt].lazy5) {
        dat[rt << 1].lazy5 += dat[rt].lazy5;
        dat[rt << 1 | 1].lazy5 += dat[rt].lazy5;
        dat[rt << 1].sum5 += dat[rt].lazy5;
        dat[rt << 1 | 1].sum5 += dat[rt].lazy5;
        dat[rt].lazy5 = 0;
    }
    if (dat[rt].lazy7) {
        dat[rt << 1].lazy7 += dat[rt].lazy7;
        dat[rt << 1 | 1].lazy7 += dat[rt].lazy7;
        dat[rt << 1].sum7 += dat[rt].lazy7;
        dat[rt << 1 | 1].sum7 += dat[rt].lazy7;
        dat[rt].lazy7 = 0;
    }
    dat[rt << 1].maxn = max(max(dat[rt << 1].sum2, dat[rt << 1].sum3), max(dat[rt << 1].sum5, dat[rt << 1].sum7));
    dat[rt << 1 | 1].maxn = max(max(dat[rt << 1 | 1].sum2, dat[rt << 1 | 1].sum3), max(dat[rt << 1 | 1].sum5, dat[rt << 1 | 1].sum7));
    return;
}

// 区间修改: [L, R] += x
void modify(int rt, int l, int r, int L, int R, int x2,int x3,int x5,int x7) {
    if (L <= l && r <= R) {  // 如果当前区间被完全包含
        dat[rt].sum2 += x2;
        dat[rt].lazy2 += x2;
        dat[rt].sum3 += x3;
        dat[rt].lazy3 += x3;
        dat[rt].sum5 += x5;
        dat[rt].lazy5 += x5;
        dat[rt].sum7 += x7;
        dat[rt].lazy7 += x7;
        return ;
    }
    
    pushdown(rt, l, r);  // 下传
    // 递归左右子树修改区间
    int mid = (l + r) >> 1;
    if (L <= mid)
        modify(rt << 1, l, mid, L, R, x2, x3, x5, x7);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, x2, x3, x5, x7);
    pushup(rt);  // 上传
    return;
}

// 区间查询:获得[L, R]的区间和
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt].maxn;  // 如果[l, r]被完全包含于[L, R]
    pushdown(rt, l, r);  // 标记下传
    // 递归加上左右子树
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res = max(res,query(rt << 1, l, mid, L, R));
    if (mid < R) res = max(res,query(rt << 1 | 1, mid + 1, r, L, R));
    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) a[i]=1;  // 读入数组
    build(1, 1, n);  // 建树
    char op[10];
    for (int i = 1, a, b, x; i <= m; ++i) {
        scanf("%s", &op);
        if (strcmp(op,"MULTIPLY")==0) {
            scanf("%d%d%d", &a, &b, &x);
            int xn[4] = {0, 0, 0, 0};
            for (int j = 0; j < 4;j++){
                while(x%prime[j]==0){
                    xn[j]++;
                    x /= prime[j];
                }
            }
            modify(1, 1, n, a, b, xn[0], xn[1], xn[2], xn[3]); // 区间修改
        }
        else {
            scanf("%d%d", &a, &b);
            printf("ANSWER %lld\n", query(1, 1, n, a, b));  // 区间查询
        }
    }
    return 0;
}

H. Delivery Route

大意:

在一张既有有向边又有无向边的图上求出每个点到源点的最短距离,保证有向边不会形成有向环

思路:

先把无向边建的图分成很多连通块,缩成超级点。再加上有向边后,整张图就变成一张dag。然后拓扑排序求最短路,当处理超级点时,用堆优化版dijkstra处理

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

typedef pair<int, int> PII;
int const N = 3e4 + 10, M = 2e5 + 10;
int e[M], ne[M], w[M], idx, h[M];
int t, r, p, source;
int id[N];
vector<int> block[N];
int bcnt;
int st[N];
int dis[N];
queue<int> q;
int din[N];

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

// 按照连通块缩点
void dfs(int u, int cnt) {
    id[u] = cnt;
    block[cnt].push_back(u);
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!id[j]) dfs(j, cnt);
    }
}

// 堆优化版dijkstra算法求连通块内的最短路
void dijkstra(int bid) { 
    priority_queue<PII, vector<PII>, greater<PII> >heap;
    for (auto b: block[bid]) heap.push({dis[b], b});
    while (heap.size()) {
        auto t = heap.top();
        heap.pop();
        int distance = t.first, ver = t.second;
        if (st[ver]) continue;
        st[ver] = 1;
        for (int i = h[ver]; i != -1; i = ne[i]) {
            int j = e[i];
            if (dis[j] > distance + w[i]) {
                dis[j] = distance + w[i];
                if (id[ver] == id[j]) heap.push({dis[j], j});  // 如果是一个超级点内才能更新
            }
            if (id[ver] != id[j]) { // 不在一个超级点内,那么需要按照拓扑排序逻辑更新
                din[id[j]]--;
                if (!din[id[j]]) q.push(id[j]);
            }
        }
    }
}

// 拓扑排序求最短路
void top_sort() {
    for (int i = 1; i <= bcnt; ++i)
        if (!din[i]) q.push(i);
    memset(dis, 0x3f, sizeof dis);
    memset(st, 0, sizeof st);
    dis[source] = 0;
    while (q.size()) {
        auto t = q.front();
        q.pop();
        dijkstra(t);
    }
}
 
int main() {
    memset(h, -1, sizeof h);
    cin >> t >> r >> p >> source;
    for (int i = 1; i <= r; ++i) {  // 读入无向边
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        add(a, b, c);
        add(b, a, c);
    }
    for (int i = 1; i <= t; ++i) {  // 把所有点按照连通块划分为超级点
        if (!id[i]) dfs(i, ++bcnt);
    }
    for (int i = 1; i <= p; ++i) {  // 读入有向边
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        add(a, b, c);
        din[id[b]]++;
    }
    top_sort();  // 拓扑排序求最短路
    for (int i = 1; i <= t; ++i) {  // 判断是否有最短路
        if (dis[i] > 0x3f3f3f3f / 2) cout << "NO PATH\n";
        else cout << dis[i] << endl;
    }
    return 0;
}

I. Base62

大意:

给出一个字符串,要求从n进制转化为m进制

思路:

短除法模板题

#include<bits/stdc++.h>

using namespace std;

const int N = 1e3 + 5;
typedef long long LL;

int n,m;
char str1[N],str2[N];
int t[N],ans[N];

int getnum(char ch) //字符转数字
{
    if(ch <= '9') return ch-'0';
    else if(ch <= 'Z') return 10+ch-'A';
    else return 36+ch-'a';
}

char getch(int num) //数字转字符
{
    if(num <= 9) return num+'0';
    else if(num <= 35) return num-10+'A';
    else return num-36+'a';
}

void solve()
{
    int len = strlen(str1);

    for(int i = 0; i < len; i++)    //先把字符串变成数组,高位->低位
        t[i] = getnum(str1[i]);
    
    int j = 0,k = 0;
    while(j < len)
    {
        for(int i = j; i < len-1; i++)  //除以m,把余数加到下一位
        {
            t[i+1] += t[i] % m * n;
            t[i] /= m;
        }
        
        ans[k++] = t[len-1]%m;      //个位数余m
        t[len-1] /= m;

        while(j < len && !t[j]) j++;    //最高位是0,j++
    }

    for(int i = 0; i < k; i++)  //逆序变成字符串
    {
        str2[i] = getch(ans[k-i-1]);
    }
}

int main()
{
    int t;
    memset(ans, 0, sizeof(ans));
    memset(str2, 0, sizeof(str2));
    cin>>n>>m;
    cin>>str1;
    solve();
    cout<<str2<<endl;
    
}

J. Toad’s Travel

咕咕咕

K. Largest Common Submatrix

大意:

给两个\(n*m\)的矩阵,矩阵的元素为1~\(n*m\)的全排列,求两个矩阵的最大相同子矩形的面积(元素数)

思路:

由于两个矩阵都是全排列,可以先标记出a矩阵中的元素在b矩阵中出现的位置,然后预处理一下a矩阵中元素向右能延伸多少,标记为l数组,然后就可以转化为经典的单调栈处理最大矩形的问题(只不过这个是旋转了90度的),还需要注意的是,需要保证向下也是能延伸的,所以还需要标记一个l2数组

#include<bits/stdc++.h>
using namespace std;
  
typedef long long LL;
  
const int N=1e3+100; 
int l[N][N],l2[N][N],a[N][N],b[N][N];
struct Node{
	int h,w;
	Node(int H,int W){
		h=H;
		w=W;
	}
};

unordered_map<int, pair<int, int>> pos;//map超时

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
            l[i][j] = 1;
            l2[i][j]=1;
        }
			
    }
	for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
			scanf("%d",&b[i][j]);
            pos[b[i][j]] = {i, j};
        }
    }		
	for(int i=1;i<=n;i++){
        for(int j=m-1;j>=1;j--)
		{
			int x1=i,y1=j;
			int x2=pos[a[i][j]].first;
			int y2=pos[a[i][j]].second;
			if(a[x1][y1+1]==b[x2][y2+1])
				l[x1][y1]=l[x1][y1+1]+1;
		}
    }	
	for(int i=n-1;i>=1;i--){
        for(int j=1;j<=m;j++){
			int x1=i,y1=j;
			int x2=pos[a[i][j]].first;
			int y2=pos[a[i][j]].second;
			if(a[x1+1][y1]==b[x2+1][y2])
				l2[x1][y1]=l2[x1+1][y1]+1;	
		}
    }
		
	int ans=0;
	for(int i=1;i<=m;i++){
		int mark=1; 
		while(mark<=n){
			stack<Node>st;
			for(int j=mark;j<=mark+l2[mark][i]-1;j++){
				int w=0;
				while(st.size()&&st.top().h>l[j][i]){
					w+=st.top().w;
					ans=max(ans,w*st.top().h);
					st.pop();
				}
				st.push(Node(l[j][i],w+1));
			}
			int w=0;
			while(st.size()){
				w+=st.top().w;
				ans=max(ans,w*st.top().h);
				st.pop();
			}
			mark+=l2[mark][i];
		}
	} 
	cout<<ans<<endl; 
    return 0;
}

L. Xian Xiang

咕咕咕

M. Crazy Cake

咕咕咕

N. Fibonacci Sequence

签到,输出前五个斐波那契数

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;

int main(){
    cout << "1 1 2 3 5" << endl;
    return 0;
}
上一篇:C语言中返回字符串函数的四种实现方法 2015-05-17 15:00 23人阅读 评论(0) 收藏


下一篇:2019 ICPC Asia Yinchuan Regional