zoukankan      html  css  js  c++  java
  • ICPC 2019 徐州网络赛

    ICPC 2019 徐州网络赛

    比赛时间:2019.9.7
    比赛链接:The Preliminary Contest for ICPC Asia Xuzhou 2019

    赛后的经验总结 // 比赛完才反应过来没看完全部题, J题树形dp没写太亏了。。。 // 几何旋律AK了,太强了Orz // 虽然我们队A了8题,但如果时间分配好,认真读题避免看错题的话,应该至少多A一题

    A. Who is better?

    题意

    两个人玩游戏,谁先消灭完敌人就赢。两条规则:1. 第一个人不能全部消灭完; 2. 后一次消灭的人数不超过前一次的两倍。
    敌人人数通过 n 个同余方程求出。
    非常明显的斐波那契博弈 + 中国剩余定理
     

    坑点

    很裸的两个知识点强行结合在一起,于是愉快地抄起板子来,一交即WA,再交继续WA...
    考虑到 CRT 计算过程中 ll 会溢出,改写 __int128,继续WA...
    最后想起洛谷上测板子时候,把合并过程中的 a[i] >= m[i] 条件注释掉,然后就A了。。。
    也就是说,测试数据认为余数比模数大也是合法的。
     

    AC代码(新的 CRT 加强模板)

    #include<iostream>
    #include<cstdio>
    #include<set>
    using namespace std;
    
    typedef long long ll;
    typedef __int128 i128;
    const ll maxn = 1e15;
    
    i128 exgcd(i128 a, i128 b, i128& x, i128& y) {
        if(b==0) {
            x = 1; y = 0;
            return a;
        }
        i128 d = exgcd(b, a%b, y, x);
        y -= (a/b) * x;
        return d;
    }
    
    i128 a[20], m[20];
    int n;
    
    i128 excrt() {
        if (n==1) {
            if (m[0]>a[0]) return a[0];
            else return -1;
        }
        i128 x, y, d;
        for (int i=1;i<n;i++) {
            d = exgcd(m[0], m[i], x, y);
            if ((a[i]-a[0])%d!=0) return -1;  
            
            i128 t = m[i] / d;
            x = ((a[i]-a[0]) / d * x % t + t) % t; 
            a[0] = x * m[0] + a[0];
            m[0] = m[0] * m[i] / d;
            a[0] = (a[0]%m[0] + m[0]) % m[0];
        }
        return a[0];
    }
    
    ll fib[100];
    int main() {
        cin>>n;
        for(int i=0;i<n;i++) {
            ll a1, a2;
            cin>>a1>>a2;
            m[i] = a1, a[i] = a2;
        }
    
        i128 x = excrt();
        if(x<=1 || x>maxn) {
            return 0 * printf("Tankernb!
    ");
        }
    
        set<ll> S;
        fib[1] = 1;
        for(int i=2;i<100;i++) {
            fib[i] = fib[i-1]+fib[i-2];
            if(fib[i]>maxn) break;
            S.insert(fib[i]);
        }
        if(S.find(x)==S.end())
            printf("Zgxnb!
    ");
        else
            printf("Lbnb!
    ");
    }
    

    B. so easy

    题意

    有 n 个点从 1~n 编号,q 次操作 (z, x) 。((1 le x < n <10^9), (1 le q<10^6))
    z = 1 时,删除点 x;z = 2 时,查询从 x 之后能到达的第一个点(包括 x )。

    思路

    n 的范围太大,线段树什么都存不了。
    卡了很长时间,一直段错误。最后队友Yu用对查询离散化+并查集A了。
     

    题解代码(使用 unordered_map

    #include<iostream>
    #include<cstdio>
    #include<unordered_map>
    using namespace std;
    const int maxn = 100010;
    unordered_map<int, int> fa;
     
    int findfa(int x) {
        if(!fa.count(x)) return x;
        return fa[x] = findfa(fa[x]);
    }
     
    int main() {
        int n, q;
        scanf("%d %d", &n, &q);
        int op, x;
        while(q--) {
            scanf("%d %d", &op, &x);
            if(op==1) {
                fa[x] = findfa(x+1);
            } else {
                int ans = findfa(x);
                if(ans>n) ans = -1;
                printf("%d
    ", ans);
            }
        }
        return 0;
    }
    

    C. Buy Watermelon

    题意

    出题人的sb英语,三个人读完题都不知道写的什么。签到题WA了三次给强行怼过了。
    大概题目想表达的是,要把西瓜分成重量都为偶数克的两部分吧。
     

    思路

    西瓜重量 w 必须为大于2的偶数。
     

    AC代码

    略。
     

    D. Carneginon

    队友Wu写的,据说是简单的KMP,也是签到题。

    AC代码

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;
    
    char s[maxn],t[maxn];
    int nexts[maxn],nextt[maxn];
    
    void getnext_t(char *ptr){
        int i,n,k;
        n=strlen(ptr);
        k=-1;
        nextt[0]=-1;
        i=0;
    
        while(i<n)
        {
            if(k==-1 || ptr[i]==ptr[k])
            {
                i++;
                k++;
                nextt[i]=k;
            }
            else k=nextt[k];
        }
    }
    
    void getnext_s(char *ptr){
        int i,n,k;
        n=strlen(ptr);
        k=-1;
        nexts[0]=-1;
        i=0;
    
        while(i<n)
        {
            if(k==-1 || ptr[i]==ptr[k])
            {
                i++;
                k++;
                nexts[i]=k;
            }
            else k=nexts[k];
        }
    }
    
    int kmps(char *a,char *b)//匹配ab两串,a为父串
    {
        int i=0,j=0;
        int len1=strlen(a);
        int len2=strlen(b);
        getnext_t(t);
        while(i<len1&&j<len2)
        {
          if(j==-1||a[i]==b[j])
            ++i,++j;
          else
            j=nextt[j];
        }
        if(j==len2)
        return  i-j;
        else
            return -1;
    }
    
    int kmpt(char *a,char *b)//匹配ab两串,t为父串
    {
        int i=0,j=0;
        int len1=strlen(a);
        int len2=strlen(b);
        while(i<len1&&j<len2)
        {
          if(j==-1||a[i]==b[j])
            ++i,++j;
          else
            j=nexts[j];
        }
        if(j==len2)
        return  i-j;
        else
            return -1;
    }
    
    bool cmp(char *a, char *b){
        int len=strlen(s);
        for(int i=0;i<len;i++) {
            if(a[i]!=b[i]) return 0;
        }return 1;
    
    }
    
    int main()
    {
        scanf("%s",s);
        getnext_s(s);
        int q;scanf("%d",&q);
        for(int i=1;i<=q;i++){
            scanf("%s",t);
            int ls=strlen(s),lt=strlen(t);
            if(ls==lt){
                if(cmp(s,t)) printf("jntm!
    ");
                else printf("friend!
    ");
            }else if(ls>lt){
                if(kmps(s,t)!=-1) printf("my child!
    ");
                else printf("oh, child!
    ");
            }else {
                if(kmpt(t,s)!=-1) printf("my teacher!
    ");
                else printf("senior!
    ");
            }
        }
        return 0;
    }
    

    E. XKC's basketball team

    题意

    定义 w[i] 的 angry 值 为 w[i] 与右边最远的满足 w[j] >= w[i] + m 之间的人数。
    求所有点的 angry 值之和。
     

    思路

    队友Yu写的,当时我沉浸在数论题中。。。
     

    AC代码

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #define inf 1000000000
    using namespace std;
    const int N = 5e5+10;
    int tree[N<<4],num[N],cnt;
    int ls[N<<4],rs[N<<4];
    void update(int l,int r,int pos,int x,int d)
    {
    	if(l==r)
    	{
    		tree[pos] = max(tree[pos],d);
    		return;
    	}	
    	int mid = (l+r)>>1;
    	if(x<=mid)
    	{
    		if(ls[pos]==0)
    			ls[pos] = ++cnt;
    		update(l,mid,ls[pos],x,d);
    	}
    	else
    	{
    		if(rs[pos]==0)
    			rs[pos] = ++cnt;
    		update(mid+1,r,rs[pos],x,d);
    	}
    	tree[pos]=max(tree[ls[pos]],tree[rs[pos]]);
    }
    int qry(int l,int r,int L,int R,int pos)
    {
    	if(L>R)return 0;
    	if(pos==0)return 0;
    	if(L<=l && r<=R)
    		return tree[pos];
    	int mid = (l+r)>>1;
    	int ret = 0;
    	if(L<=mid)
    	 	ret = max(ret,qry(l,mid,L,R,ls[pos]));
     	if(mid<R)
     		ret = max(ret,qry(mid+1,r,L,R,rs[pos]));
    	return ret;
    }
    int arr[N],hx[N];
    int head;
    int main()
    {
    	cnt = 0;
    	int n,m;scanf("%d%d",&n,&m);
    	head = ++cnt;
    	for(int i=1;i<=n;++i)
    	{
    		scanf("%d",&arr[i]);
    		hx[i] = arr[i];
    		//update(1,inf,head,arr[i],i);
    	}
    	sort(hx+1,hx+1+n);
    	int len = unique(hx+1,hx+1+n)-hx-1;
    	for(int i=1;i<=n;++i)
    	{
    		int pos = lower_bound(hx+1,hx+1+len,arr[i])-hx;
    		update(1,len,head,pos,i);
    	}
    	bool ok = 1;
    	for(int i=1;i<=n;++i)
    	{
    		if(ok==0)
    			printf(" ");
    		ok=0;
    		int rs = arr[i]+m;
    		if(rs>hx[len])
    			printf("-1");
    		else
    		{
    			int pos = lower_bound(hx+1,hx+1+len,rs)-hx;
    			//cout<<endl<<i<<" "<<rs<<" "<<hx[pos]<<" "<<pos<<endl;
    			int ans = qry(1,len,pos,len,head);
    			if(ans<=i)
    				printf("-1");
    			else
    				printf("%d",ans-i-1);
    		}	
    	}
    	puts("");
    	return 0;
    }
    

    G. Colorful String

    题意

    给定一个字符串,定义 value 为回文子串的不同字母个数,求它的全部回文子串的 value 之和。
     

    思路

    应该是字符串水题。
    我用马拉车调了半天没弄出来。字符串队友那边用回文自动机直接一发A了。
     

    AC代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long ll;
    
    const int maxn=3e5+5;
    const int N=26;
    struct Palindromic_Tree
    {
        int next[maxn][N];
        int fail[maxn];
        int cnt[maxn];//第i个节点表示的回文串出现的次数,最后要调用count函数完成计算
        int val[maxn];
        int len[maxn];//节点i表示的回文串的长度
        int S[maxn];//存放添加的字符
        bool used[26];
    
        int last;
        int n;//字符数组指针
        int p;//节点指针
        int newnode(int l)
        {
            memset(next[p],0,sizeof next[p]);
            cnt[p]=0;
            len[p]=l;
            return p++;
        }
        void init()
        {
            p=0;
            newnode(0);
            newnode(-1);
            memset(used,0,sizeof used);
            last=0;
            n=0;
            S[n]=-1;
            fail[0]=1;
        }
        int get_fail(int x)
        {
            while(S[n-len[x]-1]!=S[n])
                x=fail[x];
            return x;
        }
        void add(int c)
        {
            c-='a';
            S[++n]=c;
            int cur=get_fail(last);
            if(!next[cur][c])
            {
                int now=newnode(len[cur]+2);
                fail[now]=next[get_fail(fail[cur])][c];
                next[cur][c]=now;
            }
            last=next[cur][c];
            cnt[last]++;
        }
        void count()
        {
            for(int i=p-1;i>=0;i--)
                cnt[fail[i]]+=cnt[i];
        }
        void dfs(int now,ll num){
            for(int i=0;i<26;i++) {
                if(next[now][i]){
                    if(!used[i]) {
                        used[i]=1;
                        dfs(next[now][i],num+1);
                        used[i]=0;
                    }else dfs(next[now][i],num);
                }
            }
            val[now]=num;
        }
    }pam;
    char s[maxn];
    
    int main()
    {
        scanf("%s",s);
        pam.init();
        int len=strlen(s);
        for(int i=0;i<len;i++) pam.add(s[i]);
        pam.count();
    
        pam.dfs(0,0);
        pam.dfs(1,0);
        ll ans=0;
        for(int i=2;i<pam.p;i++) ans+=1LL*pam.cnt[i]*pam.val[i];
        printf("%lld
    ",ans);
        return 0;
    }
    

    J. Random Access Iterator (补)

    题意

    在树上按照图中 dfs 的方式,每次随机选一个儿子往下 dfs 得到树的深度。
    求程序返回树的正确深度的概率。
     

    思路

    树形dp
    比赛时候没有写,我的锅,全耗在数论题上了,结果全是通过数不超过100的难题。
    注意题目程序的 dfs 每个节点跑了 k 次,相当于独立重复实验。
    设tmp为节点 u 能达到根节点的概率, $tmp = sum {1 over k} dp[v] ( )dp[u] = 1 - (1-tmp)^k$
     

    AC代码

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<vector>
    #include<algorithm>
    using namespace std;
    const int mod = 1e9+7;
    const int maxn = 1000100;
    typedef long long ll;
    
    vector<int> G[maxn];
    ll dp[maxn], ans;
    int dep[maxn], maxDep;
    
    ll powmod(ll a, ll n) {
        ll res = 1;
        while(n) {
            if(n&1) res = res * a % mod;
            a = a * a % mod;
            n >>= 1;
        }
        return res;
    }
    
    void getDep(int u, int fa, int d) {
        dep[u] = d;
        maxDep = max(d, maxDep);
        for(int i=0;i<G[u].size();i++) {
            int v = G[u][i];
            if(v!=fa) {
                getDep(v, u, d+1);
            }
        }
    }
    
    void dfs(int u, int fa) {
    	if(dp[u]!=-1) return;
    
    	ll tmp = 0;
    	int son = G[u].size();
    	if(fa!=-1) --son;
    
    	for(int i=0;i<G[u].size();i++) {
    		int v = G[u][i];
    		if(v==fa) continue;
    
    		dfs(v, u);
    		tmp = (tmp + dp[v]*powmod(son, mod-2)%mod ) % mod;
    	}
    	dp[u] = (1-powmod((1-tmp+mod)%mod, son) + mod) % mod;
    }
    
    int main() {
    	int n; scanf("%d", &n);
    	for(int i=0;i<n-1;i++) {
            int u, v;
         	scanf("%d %d", &u, &v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
    
        getDep(1, -1, 1);
        for(int i=1;i<=n;i++) {
        	if(dep[i]==maxDep)
        		dp[i] = 1;
        	else {
        		if(G[i].size()==1)
        			dp[i] = 0;
        		else
        			dp[i] = -1;
        	}
        	// cout<<dp[i]<<endl;
        }
    	dp[1] = -1;
        dfs(1, -1);
        printf("%lld
    ", dp[1]);
        
    	return 0;
    }
    

    K. Center

    题意

    给定平面上 n 个点,求加入最少的点使全部点能够中心对称。
     

    思路

    时限2s,复杂度O(n^3*logn)的暴力枚举算法勉强能过。
    直接写完交一发,果然T了。
    改用unordered_map存两点映射后的整数,降低了 logn 的复杂度,还是T了。
    发现固定中心点的时候,剩下的点的对称点不会发生重合,直接检查是否插入新的点,带上 if(now>ans) break; 优化check过程,最后A了,几分钟之和才拿下A题。
     

    AC代码(赛后改回了set可以A)

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<set>
    #include<algorithm>
    using namespace std;
    const int maxn = 100010;
    typedef pair<int, int> pii;
    struct Point {
        int x, y;
        Point(int xx=0, int yy=0):x(xx), y(yy) {}
    }p[1010];
    
    set<pii> S;
    
    int main() {
        int n; scanf("%d", &n);
        for(int i=0;i<n;i++) {
            scanf("%d %d", &p[i].x, &p[i].y);
            S.insert(make_pair(p[i].x, p[i].y));
        }
        
        int ans = n;
        for(int i=0;i<n;i++) {
            for(int j=i+1;j<n;j++) {
                int now = 0;
                for(int k=0;k<n;k++) {
                    if(S.find(make_pair(p[i].x+p[j].x-p[k].x, p[i].y+p[j].y-p[k].y))==S.end())
                        ++now;
                    if(now>ans) break; // 没有会TLE
                }
                ans = min(ans, now);
            }
        }
    
        for(int i=0;i<n;i++) {
            int now = 0;
            for(int j=0;j<n;j++) {
                if(i==j) continue;
                if(S.find(make_pair(2*p[i].x-p[j].x, 2*p[i].y-p[j].y))==S.end())
                    ++now;
                if(now>ans) break;
            }
            ans = min(ans, now);
    
        }
        printf("%d
    ", ans);
        return 0;
    }
    

    M. Longest subsequence

    题意

    给定两个字符串 s, t ,求 s 串中满足字典序大于 t 串的最长子序列长度。
     

    思路

    又是字符串,大概dp吧。
    Wu读错题,让Yu白搞了快一个小时。。。
    注意是子序列不是子串啊!!!
     

    AC代码

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    const int N = 1e6+10;
    char str[N];
    int dp[N][30];
    char ptn[N];
    int main()
    {
    	int n,m;scanf("%d%d",&n,&m);
    	scanf("%s%s",str+1,ptn+1);
    	m++;
    	ptn[m]='a'-1;
    	for(int i=0;i<26;++i)
    		dp[n][i]=n*2;
    	for(int i=n-1;i>=0;i--)
    	{
    		for(int j=0;j<26;++j)
    			dp[i][j] = dp[i+1][j];
    		dp[i][str[i+1]-'a'] = i+1;
    	}
    	int ans = 0;
    	int now = 0;
    	for(int i=1;i<=m && now<=n;++i)
    	{
    			for(int j=ptn[i]-'a'+1;j<26;++j)
    				if(dp[now][j]<=n)
    					ans = max(ans,n-dp[now][j]+i);
    		now = dp[now][ptn[i]-'a'];	
    		//cout<<i<<" "<<ans<<" "<<now<<endl;	
    	}
    	if(ans)
    		cout<<ans<<endl;
    	else 
    		puts("-1");
    	return 0;
    }
    

    (未完待补题。。。)

  • 相关阅读:
    初步认识,合并集(树)
    20180918-1 词频统计
    20181011-1 每周例行报告
    2018091-2 博客作业
    项目第六天
    项目第五天
    项目第四天
    项目第三天
    总结随笔
    测试报告
  • 原文地址:https://www.cnblogs.com/izcat/p/11489186.html
Copyright © 2011-2022 走看看