杭电第四场 题解

比赛传送门
作者: fn


签到题

1001题 Calculus 微积分学

题目大意
判断级数是否收敛

考察内容
签到,思维

分析
注意到题中所给的所有函数的级数均发散。所以只需要检查是否所有的构成函数的系数均为 0 即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,m;

int main(){ 
	int t; cin>>t;
	while(t--){
		string s;
		cin>>s;
		ll len=s.size();
		
		int flag=1; // 收敛 
		
	{
		ll j=0;
		ll cnt=0;
		while(s[j]>='0' && s[j]<='9'){
			cnt=cnt*10+(s[j]-'0');
			j++;
		} 
		if(cnt!=0){
			puts("NO");
			continue;
		}
	}
	 
		int j=0;
		for(int i=0;i<len;i=j){ //
			j=i+1;
			if(s[i]=='+'){
				ll cnt=0;
				while(s[j]>='0' && s[j]<='9'){
					cnt=cnt*10+(s[j]-'0');
					j++;
				} 
				if(cnt!=0){
					flag=0; // 发散 
					break;
				}
			}
		}
		if(flag)puts("YES");
		else puts("NO");
	}
	return 0;
}

基本题

1009题 License Plate Recognition 车牌识别

题目大意
给出一个7个字符的车牌号,取到每个字符的左右端点。

考察内容
模拟,暴力

分析
扫描一遍。可以通过判断宽度或者识别汉字特征来处理第一个汉字 “川” “鄂“ 的特判。

Code1(判断宽度>9):

#include<bits/stdc++.h>
using namespace std;
int t,d;
char s[40][110];
void init()
{
    for(int i=1;i<=30;i++){
        for(int j=1;j<=100;j++){
            cin>>s[i][j];
        }
    }
    return;
}
int c(int k)
{
    for(int i=1;i<=30;i++){
        if(s[i][k]=='#') return 1;
    }
    return 0;
}
int work(int left,int right)
{
    if(left==0 || right==0) return 0;
    if(d==0 && right-left<=9) return 0;
    cout<<left<<" "<<right<<endl;
    return 1;
}
int main()
{
    cin>>t;
    int tap=1;
    while(t--)
    {
        init();
        cout<<"Case #"<<tap<<":"<<endl;
        tap++;
        d=0;
        int left=0,right=0,ll=1;
        while(ll<=100)
        {
            if(c(ll)==0 || ll==100){
                if(ll==100 && c(ll)==0) right=99;
                if(ll==100 && c(ll)==1) right=100;
                if(work(left,right)==1){
                    left=0;right=0;d++;
                }
            }
            else
            {
                if(left==0) left=ll;
                else right=ll;
            }
            ll++;
        }
    }
    return 0;
} 

Code2(识别汉字特征进行特判):

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

ll n,m;
int l[50],r[50];

int main(){ // 
//	freopen("1.out","w",stdout); //

	int t; cin>>t;
	int Case=1;
	while(Case<=t){
		memset(l,0,sizeof(l));
		memset(r,0,sizeof(r));
		
		string s[40];
		for(int i=1;i<=30;i++){
			cin>>s[i];
		}
		
		cout<<"Case #"<<Case<<":"<<endl;
		Case++;
		
		//
		int flag=0;
		int p=1,q=1;
		
		for(int j=0;j<100;j++){
			int F=0;
			for(int i=1;i<=30;i++){
				if(s[i][j]=='#'){ // 出现 
					F=1; 
				}
			}
			
			if(flag==0 && F==1){ // 起点 
				flag=1;   // 当前有字 
				l[p]=j+1; // 记录左端点 
				p++;
				
				// 特判 
				if(p==2){ // 刚刚记录了第一个汉字 
					int num[4]={0};
					int pos[40]={0};
					int p1=1;  
					for(int i=1;i<=30;i++){
						if(s[i][j]=='#'){
							num[1]++;
							pos[p1]=i;
							p1++;
						}
						if(s[i][j+1]=='#'){
							num[2]++;
						}
						if(s[i][j+2]=='#'){
							num[3]++;
						}
					}
					
					// 判断川 
					if(num[1]==2 && num[2]==4 && num[3]==11 && pos[2]==pos[1]+1){ 
						if(s[pos[1]][j+1]=='#' && s[pos[1]-1][j+1]=='#' && s[pos[1]-2][j+1]=='#' && s[pos[1]-3][j+1]=='#'){
							j+=13; // 跳过这段 
						}
					}
					// 判断鄂
					if(num[1]==6 && num[2]==5 && num[3]==6 && pos[2]==pos[1]+1){ 
						if(s[pos[1]][j+1]=='#' && s[pos[1]][j+2]=='#' && s[pos[1]][j+3]=='#'){ //
							j+=13; // 跳过这段 
						}
					}
				}
				continue;
			}
			
			if(flag==1 && F==0){
				flag=0;
				r[q]=j; // 记录右端点 
				q++;
			}
		}
		
		
		if(flag==1){ // 当前有字 
			r[7]=100; // 最后一个位子放上 
		} 
		
		for(int i=1;i<=7;i++){
			cout<<l[i]<<' '<<r[i]<<endl;
		}
	}
	return 0;
}

1002题 Kanade Loves Maze Designing Kanade热爱迷宫设计

考察内容
dfs

分析
注意到允许一个 O ( N 2 ) O(N^{2}) O(N2) 的算法,考虑用 ±1 法统计每种颜色。记 c i c_{i} ci 为颜色 i i i 的出现次数, r r r 为从某点处开始 DFS 到 i i i 点 得到的答案,进入该点时,给 c i c_{i} ci 加一,如果这种颜色第一次出现,则给 r r r 加一,记录答案,退出该点时,如果给 r r r 减一后这种颜色不会出现,则 c i c_{i} ci 减一。

#include<bits/stdc++.h>
#define MAXN 19560929
#define MOD1 1000000007
#define MOD2 1000000009
using namespace std;
int t,n,v[2001],num[2001],vv[2001];
int A[2001][2001];
vector<int> G[2001];
void init()
{
    cin>>n;
    for(int i=1;i<=n;i++) G[i].clear();
    for(int i=1;i<=n-1;i++){
        int a;
        cin>>a;
        G[i+1].push_back(a);
        G[a].push_back(i+1);
    }
    for(int i=1;i<=n;i++) {cin>>v[i];}
    return;
}
long long work(int k,long long mod)
{
    long long sum=0,d=1;
    for(int j=1;j<=n;j++)
    {
        if(j!=1) d=(d*MAXN)%mod;
        sum=(sum+d*A[k][j])%mod;
    }
    return sum;
}
void dfs(int st,int k,int nn,int num1[2001])
{
    vv[k]=1;
    if(num1[v[k]]==0){
        nn++;
    }
    num1[v[k]]++;
    A[st][k]=nn;
    
    for(int i=0;i<G[k].size();i++){
        int tt=G[k][i];
        if(vv[tt]==0){
        dfs(st,tt,nn,num1);
        num1[v[tt]]--;
        }
    }
    return;
}
int main()
{
    cin>>t;
    while(t--)
    {
        init();
        for(int i=1;i<=n;i++) {
	        memset(num,0,sizeof(num));
    	    memset(vv,0,sizeof(vv));
        	num[v[i]]++;    
        	dfs(i,i,1,num);
        }
        
        for(int i=1;i<=n;i++)
        {
            cout<<work(i,MOD1)<<" "<<work(i,MOD2)<<endl;
        }
    }
    return 0;
}

进阶题

1008题 Lawn of the Dead 死亡草坪

题目大意
给定一张 n ∗ m n*m nm 的地图,僵尸从 ( 1 , 1 ) (1,1) (1,1) 出发向右和向下走。地图上有 k k k 个地雷。求僵尸能经过的单元格总数。

考察内容
线段树

分析
考虑所有点的个数减去不能到达的点的个数,即为可以到达的点的个数。

根据题意,有地雷的地方是不可以到达的。由于僵尸只会向右和向下走,当某个点的左边和上方都不可达时,该点不可达,并会对自己右边的点和下方的点造成影响。

由于空间很大但地雷数有限,可以从上往下逐行对每一行的地雷排序后进行处理。对每个地雷,找到从自己的右上角点 ( x − 1 , y + 1 ) (x-1,y+1) (x1,y+1) 开始的从左往右的连续不可达区域的范围,那么 x x x 这行的这个范围也不可达。可以用线段树来实现区间查询和区间覆盖。每一行处理完后查询该行不可达的点数,累加后用总点数减即得到答案。

#include<bits/stdc++.h>
using namespace std;
#define ls (x<<1)
#define rs (x<<1|1)
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
vector<int>e[N];
int tr[2][N << 2], lz[2][N << 2];

void push_down(int f, int x, int l, int r, int mid) {
	if (lz[f][x] == -1)return;
	tr[f][ls] = lz[f][x] * (mid - l + 1);
	tr[f][rs] = lz[f][x] * (r - mid);
	lz[f][ls] = lz[f][rs] = lz[f][x];
	lz[f][x] = -1;
}
void update(int f,int x, int l, int r, int L, int R, int v) {
	if (L <= l && R >= r) {
		tr[f][x] = (r - l + 1) * v;
		lz[f][x] = v;
		return;
	}
	int mid = (l + r) >> 1;
	push_down(f, x, l, r, mid);
	if (R <= mid)update(f, ls, l, mid, L, R, v);
	else if (L > mid)update(f, rs, mid + 1, r, L, R, v);
	else {
		update(f, ls, l, mid, L, mid, v);
		update(f, rs, mid + 1, r, mid + 1, R, v);
	}
	tr[f][x] = tr[f][ls] + tr[f][rs];
}
int query(int f, int x, int l, int r, int L, int R) {
	if (!tr[f][x])return inf;
	if (l == r)return l;
	int mid = l + r >> 1;
	push_down(f, x, l, r, mid);
	if (L <= l && R >= r) {
		if (tr[f][ls] > 0) return query(f, ls, l, mid, l, mid);
		else return query(f, rs, mid + 1, r, mid + 1, r);
	}
	else {
		if (R <= mid)return query(f, ls, l, mid, L, R);
		else if (L > mid)return query(f, rs, mid + 1, r, L, R);
		else return min(query(f, ls, l, mid, L, mid), query(f, rs, mid + 1, r, mid + 1, R));
	}
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int n, m, k;
		scanf("%d %d %d", &n, &m, &k);
		for (int i = 1; i <= n; ++i)e[i].clear();
		for (int i = 1; i <= (m << 2); ++i) {
			tr[0][i] = tr[1][i] = 0;
			lz[0][i] = lz[1][i] = -1;
		}
		for (int i = 0; i < k; ++i) {
			int x, y;
			scanf("%d %d", &x, &y);
			e[x].push_back(y);
		}
		long long ans = 0;
		update(0, 1, 1, m, 1, 1, 1);
		for (int x = 1; x <= n; ++x) {
			int l = 0;
			sort(e[x].begin(), e[x].end());
			for (auto& y : e[x]) {
				if (y - 1 >= l + 1) {
					int pos = query((x & 1) ^ 1, 1, 1, m, l + 1, y - 1);
					if (pos != inf)update(x & 1, 1, 1, m, pos, y - 1, 1);
				}
				l = y;
			}
			if (l + 1 <= m) {
				int pos = query((x & 1) ^ 1, 1, 1, m, l + 1, m);
				if (pos != inf)update(x & 1, 1, 1, m, pos, m, 1);
			}
			ans += tr[x & 1][1];
			update((x & 1) ^ 1, 1, 1, m, 1, m, 0);
		}
		printf("%lld\n", ans);
	}
	return 0;
}

1004题 Display Substring 显示子串

题目大意
给定一个字符串和每个字母的权重,输出权重第k小的连续子串。

样例输入

2
5 5
ababc
3 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
5 15
ababc
3 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

样例输出

4
-1

考察内容
后缀数据结构,二分答案

分析
二分答案,然后使用任意一种后缀数据结构 check 即可。

如何 check?
解一:后缀数组:所有后缀的所有前缀即所有子串。我们遍历后缀数组,对于每个后缀,其越长的前缀能
耗越大,于是可以二分找到能耗小于等于要 check 的值的前缀的个数,再减去重复部分即可。而每个后缀被重复统计的部分就是 height 数组对应的值。

解二:后缀自动机/后缀树

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

const int N = 100000 + 9;

char s[N];
int n, c[26];
long long k;

template <int N, int sigma> struct Suffix_Automaton {
    int tot, last, nxt[N * 2][sigma], len[N * 2], link[N * 2];
    int sum[N], pos[N * 2];
    vector<int> ch[N * 2];
    void init(int n) {
        tot = last = 0; link[0] = -1;
        memset(nxt, 0, (n * 2 + 5) * sigma * sizeof(int));
        memset(pos, 0, (n * 2 + 5) * sizeof(int));
        for (int i = 0; i < n * 2 + 5; ++i) ch[i].clear();
    }
    void add_char(int c) {
        int p = last, cur = last = ++tot; len[cur] = len[p] + 1;
        while (~p && !nxt[p][c]) { nxt[p][c] = cur; p = link[p]; }
        if (p == -1) { link[cur] = 0; return; }
        int q = nxt[p][c];
        if (len[q] == len[p] + 1) { link[cur] = q; return; }
        int _q = ++tot; len[_q] = len[p] + 1;
        memcpy(nxt[_q], nxt[q], sigma * sizeof(int));
        link[_q] = link[q]; link[q] = link[cur] = _q;
        while (~p && nxt[p][c] == q) { nxt[p][c] = _q; p = link[p]; }
    }
    void dfs(int u) {
        for (int v : ch[u]) dfs(v);
        if (!pos[u]) pos[u] = pos[ch[u][0]];
    }
    void add_string(const char s[], int n) {
        for (int i = 1; i <= n; ++i) {
            sum[i] = sum[i - 1] + c[s[i] - 'a'];
            add_char(s[i] - 'a');
            pos[last] = i;
        }
        for (int i = 1; i <= tot; ++i) {
            ch[link[i]].push_back(i);
        }
        dfs(0);
    }
    bool check(int C) {
        long long cnt = 0;
        for (int i = 1; i <= tot; ++i) {
            int P = pos[i], L = P - len[i] + 1, R = P - len[link[i]];
            while (L < R) {
                int M = (L + R) >> 1;
                if (sum[P] - sum[M - 1] <= C) R = M;
                else L = M + 1;
            }
            if (sum[P] - sum[L - 1] <= C) cnt += P - len[link[i]] - L + 1;
        }
        return cnt >= k;
    }
};

Suffix_Automaton<N, 26> sam;

void solve() {
    scanf("%d %lld", &n, &k);
    scanf("%s", s + 1);
    for (int i = 0; i < 26; ++i) {
        scanf("%d", c + i);
    }
    sam.init(n);
    sam.add_string(s, n);
    int L = 1, R = sam.sum[n];
    while (L < R) {
        int M = (L + R) >> 1;
        if (sam.check(M)) R = M;
        else L = M + 1;
    }
    if (sam.check(L)) printf("%d\n", L);
    else printf("-1\n");
}

int main() {
    int T; scanf("%d", &T);
    while (T--) solve();
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值