2021杭电多校第四场

Calculus

签到题。看题解说都是收敛的,不过似乎我觉得有3个收敛的,跟c的取值有关,判断一下就好了。

#include<bits/stdc++.h>

using namespace std;

string s;

void solve()
{
    cin >> s;
    bool flag = true;
    for(int i = 0;i < s.size();i ++ )
    {
        if(i == 0)
        {
            if(s[0] == '0') flag = true;
            else if(s[0] == '1')
            {
                if(s[1] == '+' || s[1] == '/' || s[1] == '^') flag = true;
                else
                {
                    flag = false;
                    break;
                }
            }
            else
            {
                if(s[1] == '+' || s[1] == '/') flag = true;
                else
                {
                    flag = false;
                    break;
                }
            }
        }
        else
        {
            if(s[i] == '+')
            {
                if(s[i + 1] == '0') flag = true;
                else if(s[i + 1] == '1')
                {
                    if(s[i + 2] == '+' || s[i + 2] == '/' || s[i + 2] == '^') flag = true;
                    else
                    {
                        flag = false;
                        break;
                    }
                }
                else
                {
                    if(s[i + 2] == '+' || s[i + 2] == '/') flag = true;
                    else
                    {
                        flag = false;
                        break;
                    }
                }
            }
        }
    }
    if(flag) puts("YES");
    else puts("NO");
}

int main()
{
    int t;
    cin >> t;
    while(t -- )
    {
        solve();
    }
    return 0;
}

License Plate Recognition

搞心态题,不想多说什么,比赛的时候差不多调了1个半小时,还是没出来,吐了,只能怪提没看清楚。

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

int T;
char s[35][105];
int cnt[105];
int ansl[10],ansr[10];

int main()
{
    scanf("%d",&T);
    for (int tt=1;tt<=T;tt++)
    {
        memset(cnt,0,sizeof(cnt));
        for (int i=1;i<=30;i++)
        {
            scanf("%s",s[i]+1);
            for (int j=1;j<=100;j++)
            {
                if (s[i][j]=='#') cnt[j]++;
            }
        }
        int pos=100;
        for (int i=7;i>=2;i--)
        {
            while (pos>=1 && !cnt[pos]) pos--;
            ansr[i]=pos;
            while (pos>=1 && cnt[pos]) pos--;
            ansl[i]=pos+1;
        }
        while (pos>=1 && !cnt[pos]) pos--;
        ansr[1]=pos;
        pos=1;
        while (pos<=100 && !cnt[pos]) pos++;
        ansl[1]=pos;
        printf("Case #%d:\n",tt);
        for (int i=1;i<=7;i++)
        {
            printf("%d %d\n",ansl[i],ansr[i]);
        }
    }
    return 0;
}

Lawn of the Dead

这道题标程给的是线段树维护区间查询和修改,感觉有点点麻烦,可能是我不太会线段树的原因吧。给你n*m的网格,里面有k个地雷,你从左上角1,1出发,每次只能向下或者向右走,问你最多能走多少个格子。首先可以考虑一下,当前行只与上一行能走到的格子有关,稍加分析就可以得到代码中的公式。至于线段树,附上代码,其他地方基本都懂,但是为什么线段树的懒标记初始化为-1,没搞清楚,后面去问一下大佬们。

枚举模拟:

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

typedef long long ll;

struct node
{
    int l,r;
};

vector<int> mine[N];
int n,m,k;
vector<node> v1,v2;

void solve()
{
    ll ans = 0;
    v1.clear();
    v2.clear();
    scanf("%d %d %d",&n,&m,&k);
    for(int i = 1;i <= k;i ++ )
    {
        int x,y;
        scanf("%d %d",&x,&y);
        mine[x].push_back(y);
    }
    v1.push_back((node){1,1});
    for(int i = 1;i <= n;i ++ )
    {
        //求当前行的可行区间
        //当前行没有地雷
        vector<node> temp;
        if(mine[i].size() == 0) v2.push_back((node){1,m});
        else
        {
            sort(mine[i].begin(),mine[i].end());
            if(mine[i][0] != 1) v2.push_back((node){1,mine[i][0] - 1});
                int last = mine[i][0];//记录上一个地雷的位置
                for(int j = 1;j < mine[i].size();j ++ )
                {
                    if(mine[i][j] == last + 1)//说明地雷是连续的,就不用考虑了
                    {
                        last ++;
                        continue;
                    }
                    else
                    {
                        v2.push_back((node){last + 1,mine[i][j] - 1});
                        last = mine[i][j];
                    }
                }
                if(last != m) v2.push_back((node){last + 1,m});
        }
        //以上就是把当前行的所有暂时可行区间放入到v2中
        vector<node>::iterator it1 = v1.begin();
        vector<node>::iterator it2 = v2.begin();
        
        while(it1 != v1.end() && it2 != v2.end())
        {
            if(it1->r < it2->l) it1 ++;
            else if(it1->l > it2->r) it2 ++;
            else
            {
                temp.push_back((node){max(it1->l,it2->l),it2->r});
                ans += (ll)(it2->r - max(it1->l,it2->l) + 1ll);
                //printf("%lld\n",ans);
                it2 ++;
            }
        }
        //答案就更新完成了
        //记得更新v1
        v2.clear();
        v1.clear();
        for(auto& x : temp) v1.push_back(x);
        //printf("%lld\n",ans);
        mine[i].clear();
    }
    printf("%lld\n",ans);
}


int main()
{
    int t;
    cin >> t;
    while(t -- )
    {
        solve();
    }
    return 0;
}

线段树:

#include <bits/stdc++.h>
#include <stdio.h>
#define N 200005
using namespace std;
int n, m, k;
vector<int> p[N];
struct SegmentTree {
    int p, l, r, val, lmax, tag;//lmax为从这个节点对应的区间的左端点开始的最长连续1的长度(最长不超过区间长度)
} t[2][(N << 2)];
void build(int x, int p, int l, int r) {
    t[x][p].l = l, t[x][p].r = r;
    t[x][p].tag = -1;
    if(l == r) {
        t[x][p].val = t[x][p].lmax = 0;
        t[x][p].tag = -1;//!!!不能初始化为0,因为此处tag有清零的作用
        return;
    }
    int mid = (l + r) >> 1;
    build(x, 2 * p, l , mid);
    build(x, 2 * p + 1, mid + 1, r);
    t[x][p].val = t[x][2 * p].val + t[x][2 * p + 1].val;
    t[x][p].lmax = 0;
}
void spread(int x, int p) {
    if(t[x][p].tag != -1) {//打了标记
        t[x][2 * p].val = (t[x][2 * p].r - t[x][2 * p].l + 1) * t[x][p].tag;
        t[x][2 * p].lmax = (t[x][2 * p].r - t[x][2 * p].l + 1) * t[x][p].tag;//不要忘了更新 tag为0就直接把子树的lmax清零
        t[x][2 * p + 1].val = (t[x][2 * p + 1].r - t[x][2 * p + 1].l + 1) * t[x][p].tag;
        t[x][2 * p + 1].lmax = (t[x][2 * p + 1].r - t[x][2 * p + 1].l + 1) * t[x][p].tag;
        t[x][2 * p].tag = t[x][p].tag;
        t[x][2 * p + 1].tag = t[x][p].tag;
        t[x][p].tag = -1;//清除标记
    }
}
void modify(int x, int p, int L, int R, int v) {
    if(t[x][p].l >= L && t[x][p].r <= R) {
        t[x][p].val = (t[x][p].r - t[x][p].l + 1) * v;
        t[x][p].lmax = (t[x][p].r - t[x][p].l + 1) * v;
        t[x][p].tag = v;
        return;
    }
    spread(x, p);
    int mid = (t[x][p].l + t[x][p].r) >> 1;
    if(L <= mid) modify(x, 2 * p, L, R, v);
    if(R > mid) modify(x, 2 * p + 1, L, R, v);
    t[x][p].val = t[x][2 * p].val + t[x][2 * p + 1].val;//val可以直接维护
    if(t[x][2 * p].val == (t[x][2 * p].r - t[x][2 * p].l + 1)) t[x][p].lmax = t[x][2 * p].val + t[x][2 * p + 1].lmax;
    //如果左子树表示的区间全部不可达,那么当前节点的lmax就是左子树区间长度+右子树的lmax
    else t[x][p].lmax = t[x][2 * p].lmax;
    //否则就是左子树的lmax(因为这样左右子树之间必然有可达位置,就不用管右子树了
    return;
}
int query(int x, int p, int l, int r) {
    if(t[x][p].l == l && t[x][p].r == r) {//直接覆盖要查询的区间
        return t[x][p].lmax;
    }
    spread(x, p);
    int mid = (t[x][p].l + t[x][p].r) >> 1;
    if(r <= mid) return query(x, 2 * p, l, r);//注意这里要查询的区间不变,因为还没真正查询!!!!
    if(l > mid) return query(x, 2 * p + 1, l, r);
    int tmp = query(x, 2 * p, l, mid);//首先查询左子树,如果左子树表示的区间全部不可达再查询右子树,否则直接返回即可
    if(tmp == mid - l + 1) return tmp + query(x, 2 * p + 1, mid + 1, r);//这里的l和r要变!因为查询区间已经被分割为两个待查询的子区间了!
    else return tmp;
}
signed main() {
    int T;
    cin >> T;
    while (T--) {
        scanf("%d%d%d", &n, &m, &k);
        build(0, 1, 1, m);
        build(1, 1, 1, m);
        for (int i = 0; i <= n + 10; i++) p[i].clear();
        for (int i = 1; i <= k; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            p[u].push_back(v);
        }
        //滚动数组的思想,开两棵线段树即可
        long long ans = 0;
        modify(0, 1, 1, m, 1);//先把第0行的边界更新
        for (int i = 1; i <= n; i++) {
            sort(p[i].begin(), p[i].end());
            p[i].push_back(m + 1);//边界,便于处理
            int lst = 0;//lst表示同行中前一个地雷的位置 每行一开始是0
            modify((i & 1), 1, 1, m, 0);//先把之前的信息清零
            for (int j = 0; j < p[i].size(); j++) { 
                int x = p[i][j];
                if(i == 1 && lst == 0) {//第一行一开始的一部分一定可达,更新lst后忽略即可
                    lst = x;
                    continue;
                }
                int lpos = lst + 1, rpos = x - 1, len;
                if(lpos <= rpos) {
                    len = query(1 ^ (i & 1), 1, lpos, rpos);
                    ans += 1ll * len;
                } else len = 0;
                if(!(lpos - 1 == 0 && lpos - 1 + len == 0)) //最左边的边界不要标记
                    modify(i & 1, 1, lpos - 1, lpos - 1 + len, 1);//把左侧的地雷也标记上
                lst = x;//不要忘记更新
            }
        }
        cout << 1ll * n * m - ans - 1ll * k << endl;//总数减去不可达的格子减去地雷数
    }
    return 0;
}

Kanade Loves Maze Designing

给你一棵 n 个节点的树,一共有 n-1 条边,每个节点都有一个种类,
定义a[i,j] 为 在 i->j 这条路径上,共有几种不同种类的节点。
这个公式中 x^ j-1 ,可以用 快速幂去求 , 主要难点在于求 a[i,j]
注意到最后输出的形式比较特殊,是每一个点到其他各个点的树上路径的答案进行运算得到的。又注意到点的范围实际上不大(2e3),考虑对每个点dfs得到答案然后计算并输出。问题就转换成了怎么求树上每个点到树根的路径上有多少种不同的点权。因为点权的范围是1到n,因此可以用树状数组维护(桶排序的思想)。每搜索到一个点就直接统计,然后遍历这个点关联的边,如果下一个点的权值已经出现过则不modify,如果没有出现过则让这个位置+1,说明这个位置对应的权值出现过则不用管。注意如果dfs前更新过,dfs完一定要回溯。
比赛的时候也不知道怎么的,超时了。。。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
using namespace std;
typedef long long ll;
const int maxn=2e4+5;
int n,tol,head[maxn],vis[maxn];
int p[maxn],c[maxn];
int f[2005][2005];
int cnt[20005];
struct node{
    int to,next;
}edge[maxn];
void add_edge(int u,int v)
{
    edge[tol].to=v;   
    edge[tol].next=head[u];
    head[u]=tol++;
}
const int mod1=1e9+9;
const int mod2=1e9+7;
int ans = 0;
ll p1[maxn];
ll p2[maxn];
void dfs(int x , int fa , int root)
{
	cnt[c[x]]++;
	if(fa==0) f[root][x] = 1;
	else
	{
		if(cnt[c[x]] == 1) f[root][x] = f[root][fa]+1;
		else f[root][x]=f[root][fa];
	}
	
	for(int i = head[x] ; i != -1 ; i = edge[i].next)
	{   int y = edge[i].to;
	   if( y == fa) continue;
		dfs(y , x , root);
		cnt[c[y]]--;
	}
	
}
ll ksm(ll base, ll power,int mod) 
{
    ll result = 1;
    while (power > 0) 
    {
        if (power & 1) 
        {
            result = result * base %mod;
        }
        power >>= 1;
        base = (base * base) %mod;
    }
    return result;
}

int main()
{
    int T;
   scanf("%d",&T);
    while (T --)
    {
       
        scanf("%d",&n);
        tol = 0;
        memset(head,-1,sizeof(head));
        for(int i = 2; i <= n ; i++)
        {
                scanf("%d",&p[i]);
            add_edge(i,p[i]);
            add_edge(p[i],i);
        }
        for(int i = 1; i <= n ; i++)
        {
            scanf("%d",&c[i]);
        }
        for(int i = 1; i <= n ; i++)
        {  memset(cnt,0,sizeof(cnt));
        	dfs(i,0,i);
		}
        ll res=0;
        ll res1=0;
        for(int i = 0; i <= n ; i++)
        {
        	p1[i] = ksm(19560929,i,mod2);
        	p2[i] = ksm(19560929,i,mod1);
		}
        for(int i=1;i<=n;i++){
        	
            res=0,res1=0;
            
            for(int j=1;j<=n;j++){
                res+=(f[i][j]*p1[j-1])%mod2;
                res1+=(f[i][j]*p2[j-1])%mod1;
            }
            printf("%lld %lld\n",res%mod2,res1%mod1);
        
    }
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值