The 2023 ICPC Asia Shenyang Regional Contest (The 2nd Universal Cup. Stage 13: Shenyang)

Preface

唉一大早起来爬起来打9点的训练,结果人差点被创飞了

前期直接感染猪头病毒,写什么什么挂,然后就经典红温心态爆炸

后面点外卖的时候还被麦当劳背刺了,浪费我半个小时直接把我们队罚时搞爆炸了

虽然后面徐神力挽狂澜写出字符串D,但由于时间不够了导致会写的B没能写出来(不过这题本来就很多坑点感觉不给我90min我都写不出来)

最后6题喜提银首,鉴定为被外卖坑了


A. Intro: Dawn of a New Era

把防AK题放在A题的出题人都是毒瘤


B. Turning Permutation

思路不难的一个DP题,但写起来一坨细节甚至最后还有个爆long long的坑点,直接把我创飞了

遇到字典序问题一眼想到按位确定一段前缀,考虑计算出后面随便放的方案数,就可以在\(O(n^2)\)的额外时间开销下解决字典序第\(k\)大了

现在要做的就是怎么快速求出后面随便放的方案数,这种排列计数问题最经典的trick就是按照顺序把数一个个确定

因此不难想到状态的设计,令\(f_{i,j,0/1}\)表示已经确定了\(1,2,\cdots,i\),且\(i\)这个数位于后面随便放的部分的第\(j\)个(若\(j=0\)则说明这个数已经在前缀中被确定位置了),最后一维\(0/1\)表示\(i-1\)\(i\)的左边/右边

转移的话要分巨多Case来讨论,最好一边写一边画画示意图不然很容易把自己绕进去,直接暴力写是\(O(n^3)\)的但已经可以通过,如果还要卡时间的话可以用前缀和优化成\(O(n^2)\)

最后要注意由于这题没有取模,而DP出来的方案数实际上可能很大,因此我们在DP过程中需要时刻将结果对\(k\)\(\min\)(因为我们只需要拿DP出的结果比较大小即可)

总复杂度\(O(n^5)\)

#include<cstdio>
#include<iostream>
#include<cstring>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=55;
int n,k,a[N],cs[N],pos[N],f[N][N][2]; // 0: i-1 on i's left; 1: i-1 on i's right
inline int calc(CI lim)
{
    RI i,j,k,tp; memset(f,0,sizeof(f)); int len=0;
    if (cs[1]&&cs[2])
    {
    	if (pos[1]<pos[2]) f[2][0][0]=1; else f[2][0][1]=1;
    } else if (cs[1]&&!cs[2]) len=1,f[2][1][0]=1;
    else if (!cs[1]&&cs[2]) len=1,f[2][0][1]=1;
    else len=2,f[2][1][1]=f[2][2][0]=1;
    auto fix=[&](int& x)
    {
    	return x=min(x,lim);
    };
    for (i=2;i<n;++i)
    {
		if (cs[i+1])
    	{
    		if (cs[i])
    		{
    			if (pos[i+1]<pos[i]) f[i+1][0][1]=f[i][0][0];
				else f[i+1][0][0]=f[i][0][1];
    		} else
			{
				for (j=1;j<=len;++j) fix(f[i+1][0][1]+=f[i][j][0]);
			}
    		continue;
    	}
    	for (j=0;j<=len;++j)
    	{
	        for (tp=0;tp<2;++tp) if (f[i][j][tp])
	        {
	        	if (tp==0)
	        	{
	        		for (k=1;k<=j;++k) fix(f[i+1][k][1]+=f[i][j][tp]);
	        	} else
	        	{
	        		for (k=j+1;k<=len+1;++k) fix(f[i+1][k][0]+=f[i][j][tp]);
	        	}
	        }
	    }
	    ++len;
    }
    int ret=0; if (cs[n]) ret=f[n][0][0]+f[n][0][1],fix(ret); else
	{
		for (i=1;i<=len;++i) fix(ret+=f[n][i][0]+f[n][i][1]);
	}
	return ret;
}
signed main()
{
    RI p,v,i; for (scanf("%lld%lld",&n,&k),p=1;p<n;++p)
    {
        int filled=-1;
        for (v=1;v<=n;++v) if (!cs[v])
        {
            cs[v]=1; a[p]=v; pos[v]=p;
            for (i=1;i<=n;++i) if (!cs[i]) pos[i]=p+1;
            bool flag=1; for (i=2;i<n;++i)
            if ((pos[i]-pos[i-1])*(pos[i]-pos[i+1])<0) { flag=0; break; }
            if (!flag) { cs[v]=0; continue; } int tmp=calc(k);
            if (tmp>=k) { filled=v; break; } else k-=tmp,cs[v]=0;
        }
        if (filled==-1) return puts("-1"),0; else a[p]=filled;        
    }
    for (i=1;i<=n;++i) if (!cs[i]) a[n]=i;
    for (i=1;i<=n;++i) printf("%lld ",a[i]);
    return 0;
}

C. Swiss Stage

签到,读懂题意即可(话说ACM中Dota相关的题一大堆但和LOL相关的很罕见的说)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int main()
{
    int x,y; scanf("%d%d",&x,&y);
    return printf("%d",(2-x)*(y==2?2:1)+2),0;
}

D. Dark LaTeX vs. Light LaTeX

很劲的字符串,徐神因为SAM板子抄错了挂了好久,但好在最后是发现了

由于我题目都没看因此一点不知道该怎么做,总之徐神牛逼就对了

#include <bits/stdc++.h>

using llsi = long long signed int;

#define int llsi

constexpr int $n = 100005;

int go[$n][26], fa[$n] = {0, 0}, len[$n] = {0, 0}, las, O;

int insert(char a) {
    int c = a - 'a', p = las;
    int np = las = ++O;
    len[np] = len[p] + 1;
    for(int i = 0; i < 26; ++i) go[np][i] = 0;
    for(; p && !go[p][c]; p = fa[p]) go[p][c] = np;
    
    if(!p) return fa[np] = 1, las;

    int q = go[p][c];
    if(len[q] == len[p] + 1) return fa[np] = q, las;

    int nq = ++O; len[nq] = len[p] + 1;
    for(int i = 0; i < 26; ++i) go[nq][i] = go[q][i];
    fa[nq] = fa[q], fa[np] = fa[q] = nq;
    for(; p && go[p][c] == q; p = fa[p]) go[p][c] = nq;
    return las;
}

int siz[$n];
std::vector<int> ch[$n];
std::vector<llsi> S[$n]; 

llsi ans1 = 0, ans2 = 0;

int curState, curLen;

void move(char a, int lenLimit) {
    const int c = a - 'a';
    while(curState > 1 && !go[curState][c]) curState = fa[curState], curLen = len[curState];
    // assert(len[fa[curState]] < curLen);
    // assert(curLen <= len[curState]);
    if(!go[curState][c]) { curLen = 0; return ; }
    curState = go[curState][c],
    curLen += 1;
    if(curLen > lenLimit) {
        curLen -= 1;
        if(curLen <= len[fa[curState]]) curState = fa[curState];
    }
    // std::cerr << "curLen = " << curLen << std::endl;
    return ;
}

void dfs1(int now) { for(auto ch: ch[now]) dfs1(ch), siz[now] += siz[ch]; }
void dfs2(int now) {
    if(now == 1) S[now] = {0};
    else {
        S[now] = S[fa[now]];
        for(int i = len[fa[now]] + 1; i <= len[now]; ++i) S[now].push_back(S[now].back() + siz[now]);
    }
    for(auto ch: ch[now]) dfs2(ch);
}

void solve(const std::string &s, const std::string &t) {
    las = O = 1; memset(go[1], 0, sizeof(go[1])); for(auto c: t) insert(c);

    // std::cerr << "debug O = " << O << char(10);

    for(int i = 1; i <= O; ++i) siz[i] = 0, ch[i].clear();
    for(int i = 2; i <= O; ++i) ch[fa[i]].push_back(i);
    for(int i = 0, sta = 1; i < t.size(); ++i) siz[sta = go[sta][t[i] - 'a']] += 1;
    dfs1(1), dfs2(1);

    // for(int i = 1; i <= O; ++i) {
    //     std::cerr << "debug S[" << i << "]: ";
    //     for(int j = 0; j < S[i].size(); ++j)
    //         std::cerr << S[i][j] << char(j == S[i].size() - 1 ? 10 : 32);
    // }

    for(int l = 1; l <= s.size(); ++l) {
        curState = 1, curLen = 0;
        for(int i = 0; i < l; ++i) move(s[i], l);

        int r = l;
        while(r < s.size() && r < 2 * l - 1 && s[r] == s[r - l]) r += 1;

        for(int i = l; ; ++i) {
            if(curLen >= i + l - r) {
                if(curLen > len[curState]) {
                    std::cerr << "curLen = " << curLen << ", l = " << l << ", len = " << len[curState] << std::endl;
                    assert(curLen <= len[curState]);
                }
                ans1 += S[curState][curLen];
                if(i + l - r - 1 >= 0)
                    ans1 -= S[curState].at(i + l - r - 1);
            }
            if(curLen == l) ans2 += siz[curState];

            if(i == s.size()) break;
            if(r < s.size() && r == i) r += 1;
            while(r < s.size() && r < i + l && s[r] == s[r - l]) r += 1;
            move(s[i], l);
        }
    }
}

int32_t main() {
    std::string s, t;
    std::cin >> s >> t;
    solve(s, t);
    std::reverse(s.begin(), s.end()); std::reverse(t.begin(), t.end());
    solve(t, s);
    // std::cerr << "debug ans1 = " << ans1 << ", ans2 = " << ans2 << char(10);
    std::cout << ans1 - ans2 / 2 << std::endl;
    return 0;
}

E. Sheep Eat Wolves

签到题,但我脑抽了有个地方漏写了还WA了一发

注意到可以拿一个三元组\((x,y,0/1)\)来表示一个状态,其中\(x,y\)分别表示在左岸上的羊/狼的数量,\(0/1\)表示当前人在左岸/右岸

每次转移直接枚举带走几只羊/狼即可,复杂度大约是\(O(x^2y^2)\)

#include<cstdio>
#include<iostream>
#include<queue>
#include<utility>
#include<cstring>
#include<array>
#define RI register int
#define CI const int&
using namespace std;
typedef array <int,3> tri;
const int N=105;
int n,m,p,q,dis[N][N][2];
int main()
{
    RI i,j; scanf("%d%d%d%d",&n,&m,&p,&q); memset(dis,-1,sizeof(dis));
    queue <tri> Q; Q.push({n,m,0}); dis[n][m][0]=0;
    while (!Q.empty())
    {
        auto [x,y,tp]=Q.front(); Q.pop();
        //printf("dis[%d][%d][%d] = %d\n",x,y,tp,dis[x][y][tp]);
        if (x==0) return printf("%d",dis[x][y][tp]),0;
        if (tp==0)
        {
            for (i=0;i<=min(p,x);++i) for (j=0;j<=min(p-i,y);++j)
            {
                if (x-i>0&&y-j>x-i+q) continue;
                if (dis[x-i][y-j][tp^1]==-1)
                dis[x-i][y-j][tp^1]=dis[x][y][tp]+1,Q.push({x-i,y-j,tp^1});
            }
        } else
        {
            for (i=0;i<=min(p,n-x);++i) for (j=0;j<=min(p-i,m-y);++j)
            {
                if (n-x-i>0&&m-y-j>n-x-i+q) continue;
                if (dis[x+i][y+j][tp^1]==-1)
                dis[x+i][y+j][tp^1]=dis[x][y][tp]+1,Q.push({x+i,y+j,tp^1});
            }
        }
    }
    return puts("-1"),0;
}

F. Ursa Minor

沟槽的原来还有个大数据结构题藏着,但这个过题人数我连题目都懒得看


G. Military Maneuver

沟槽的原来还有个几何题藏着,但比赛的时候完全没看


H. Line Graph Sequence

沟槽的原来还有个图论题藏着,而且好像是剩下的题中最可做的一个了,但无所谓我会开摆


I. Three Rectangles

这题真是一眼看上去就很可做,但一眼就知道是很麻烦的分类讨论

祁神比赛的时候基本想出了思路但由于机时不够没得写,坐等祁神补题

Upt:补完力,思路啥的都没问题,就是缺点时间

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

using pii = pair<int, int>;
#define ft first
#define sd second

const int MOD = (int)1e9+7;

int t;
pii rect[4];

void trans(){for (int i=0; i<4; ++i) swap(rect[i].ft, rect[i].sd);}
int freedom(int x){
	auto &[H, W]=rect[3];
	auto &[h, w]=rect[x];
	return (H-h+1)*(W-w+1)%MOD;
}

int solve(){
	cin >> rect[3].ft >> rect[3].sd;
	for (int i=0; i<3; ++i) cin >> rect[i].ft >> rect[i].sd;
	int full=-1;
	auto &[H, W] = rect[3];
	for (int i=0; i<3; ++i){
		if (rect[i].ft==H && rect[i].sd==W) full=i;
	}
	
	if (full>=0){
		int ans=1;
		for (int i=0; i<3; ++i) if (i!=full) ans=ans*freedom(i)%MOD;
		return ans;
	}
	
	for (int z=0; z<2; ++z){
		int cnt=0;
		int ans=0;
		trans();
		for (int i=0; i<3; ++i) if (rect[i].ft==H){++cnt;}
		if (3>cnt) continue;
		
		if (rect[0].sd+rect[1].sd+rect[2].sd<W) return 0;
//		printf("11111\n");
//			for (int x=0; x<4; ++x) printf("i=%lld (%lld %lld)\n", x, rect[x].ft, rect[x].sd);
		ans=0;
		for (int i=0; i<3; ++i){
			for (int j=0; j<3; ++j) if (i!=j){
				int k=0; while (k<3 && (k==i || k==j)) ++k;
				auto &[h, w] = rect[k];			
				if (rect[i].sd+rect[j].sd>=W) ans = (ans+(W-w-1))%MOD;
				else{
					int w1=rect[i].sd, w2=W-rect[j].sd;
					int l1 = ((1+w)>=w2 ? 1 : w2-w);
					int r1 = ((w1+w)>=W ? W-w-1 : w1);
					int l2 = ((w2-w)>=1 ? w2 : 1+w);
					int r2 = ((W-w-1)<=w1 ? W-1 : w1+w);
//					printf("i=%lld j=%lld l1=%lld r1=%lld l2=%lld r2=%lld\n", i, j, l1, r1, l2, r2);
					ans =  (ans+(r1-l1+1)%MOD)%MOD;
				}
			}
			if (rect[i].sd+max(rect[(i+1)%3].sd, rect[(i+2)%3].sd) >= W) ans=(ans+2)%MOD;
		}
		return ans;
	}
	
	for (int z=0; z<2; ++z){
		trans();
		for (int i=0; i<3; ++i) if (rect[i].ft==H){
	//		printf("i=%lld\n", i);
	//		printf("H=%lld W=%lld\n", H, W);
			for (int j=0; j<3; ++j) if (j!=i && rect[j].ft==H){
				int k=0; while (k<3 && (k==i || k==j)) ++k;
	//			printf("i=%lld j=%lld free(%lld)=%lld\n", i, j, k, freedom(k));
				if (rect[i].sd+rect[j].sd>=W){
					return freedom(k)*2%MOD;
				}else return 0;	
			}
		}
	}
	
	for (int z=0; z<2; ++z){
		trans();
		for (int i=0; i<3; ++i) if (rect[i].ft==H){
			if (rect[(i+1)%3].ft+rect[(i+2)%3].ft>=H && min(rect[(i+1)%3].sd, rect[(i+2)%3].sd)+rect[i].sd>=W){
				return 4;
			}
		}
	}
	return 0;
}

signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cin >> t;
	while (t--) cout << solve() << '\n';
	return 0;	
}


J. Graft and Transplant

诈骗题,经典\(O(n)\)做法放个\(n\le 50\)的数据范围来骗人

注意到如果我们操作的两个点其中有一个是叶子节点,那么操作完后一定会得到和原来同构的树

否则此时必然会多出一个叶子节点,因此和原来一定不同构

只要看非叶节点数量的奇偶性即可,注意特判\(n\le 3\)的情况

#include <bits/stdc++.h>

int du[51];

int main() {
    int n; std::cin >> n;
    if(n <= 3) return std::cout << "Bob\n", 0;
    for(int i = 1, a, b; i < n; ++i) {
        std::cin >> a >> b;
        du[a] += 1, du[b] += 1;
    }
    int c = 0;
    for(int i = 1; i <= n; ++i) c += (du[i] > 1);
    std::cout << (c & 1 ? "Bob\n" : "Alice\n");
    return 0;
}

K. Maximum Rating

只能说写的时候脑子抽风了WA了好几发才过,就这水平也敢去给新人讲DS?

首先经典猜结论,我们求出这些数最少/最多能将max rating改变的次数,则在这个区间中的每一种改变次数都一定能被构造出来

改变次数最多的放法很显然,直接把正数全部先放了即可,个数就是正数的数量

改变次数最少的放法则需要将非正数全部先放,然后将正数从小到大接上去放

接下来我们需要找出正数的长度最短的前缀,使得这个前缀的数和大于非正数之和的绝对值

可以把所有正数的值离散化后扔到权值线段树上,询问的话直接在线段树上二分即可,注意这里对于叶子节点的处理要特别注意(因为一个点可能被加了多次)

总复杂度\(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=400005;
int n,m,q,a[N],x[N],y[N]; vector <int> rst;
class Segment_Tree
{
    private:
        int sz[N<<2],sum[N<<2];
    public:
        #define TN CI now=1,CI l=1,CI r=m
        #define LS now<<1,l,mid
        #define RS now<<1|1,mid+1,r
        inline void modify(CI pos,CI mv,TN)
        {
            sz[now]+=mv; sum[now]+=mv*rst[pos-1]; if (l==r) return;
            int mid=l+r>>1; if (pos<=mid) modify(pos,mv,LS); else modify(pos,mv,RS);
        }
        inline int query(CI tar,TN)
        {
            //printf("[%lld,%lld] : sz = %lld ; sum = %lld\n",l,r,sz[now],sum[now]);
            if (sum[now]<=tar) return -1;
            if (l==r) return tar/rst[l-1]+1; int mid=l+r>>1;
            if (sum[now<<1]>tar) return query(tar,LS);
            else return sz[now<<1]+query(tar-sum[now<<1],RS);
        }
        #undef TN
        #undef LS
        #undef RS
}SEG;
signed main()
{
    RI i; for (scanf("%lld%lld",&n,&q),i=1;i<=n;++i)
    if (scanf("%lld",&a[i]),a[i]>0) rst.push_back(a[i]);
    for (i=1;i<=q;++i) if (scanf("%lld%lld",&x[i],&y[i]),y[i]>0) rst.push_back(y[i]);
    sort(rst.begin(),rst.end()); rst.erase(unique(rst.begin(),rst.end()),rst.end());
    auto find=[&](CI x)
    {
        return lower_bound(rst.begin(),rst.end(),x)-rst.begin()+1;
    };
    int sum=0,cnt=0; m=rst.size();
    for (i=1;i<=n;++i) if (a[i]<=0) sum-=a[i]; else a[i]=find(a[i]),SEG.modify(a[i],1),++cnt;
    for (i=1;i<=q;++i)
    {
        if (a[x[i]]<=0) sum+=a[x[i]]; else SEG.modify(a[x[i]],-1),--cnt;
        if (y[i]<=0) sum-=y[i],a[x[i]]=y[i]; else a[x[i]]=find(y[i]),SEG.modify(a[x[i]],1),++cnt;
        int res=SEG.query(sum); printf("%lld\n",cnt-(res==-1?0:cnt-res+1)+1);
        //printf("L=%lld R=%lld\n",res==-1?0:cnt-res+1,cnt);
    }
    return 0;
}

L. Rook Detection

害怕,CF上没一个人过的题目


M. Outro: True Love Waits

纯手玩题,首先不难发现题目等价于从\(0\)出发走到\(s\oplus t\),然后之后再反复走到\(s\oplus t\)的过程

手玩一下会发现一个很重要的性质,就是从低到高可以每两位划分为一组,最后走路时候每组的变化形如\(00\to 01\to 11\to 10\to 00\),即在四进制下循环变化

先考虑怎么判无解,不难发现这和\(s\oplus t\)末尾的\(0\)的数量\(z\)有关(\(0\)的话就假设末尾有无穷个\(0\)),经过次数为\(\lfloor\frac{z}{2}\rfloor+1\)

然后此时问题可以分为两步,首先是\(0\to s\oplus t\),然后接下来可以看作从\(0\)出发回到\(0\)\(k-1\)次的时间

后者的话结合前面的结论很容易发现贡献就是\(4^1+4^2+\cdots+4^{k-1}\)(不要想我一样脑抽以为这个可以开个数组预处理,还得等比数列求和一下)

前者的话手玩一下会发现每一组的贡献等于其权值(在上面的循环顺序中访问到的顺序),乘上后面部分的累计代价(形如\(4^0+4^1+\cdots+4^i\)),这个可以预处理一下

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5,mod=1e9+7;
int T,k,pw[N]; char s[N],t[N];
inline void init(CI n)
{
    RI i; for (pw[0]=i=1;i<=n;++i) pw[i]=4LL*pw[i-1]%mod;
    for (i=1;i<=n;++i) (pw[i]+=pw[i-1])%=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
    for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
int main()
{
    for (scanf("%d",&T),init(1e6);T;--T)
    {
        RI i; scanf("%s%s%d",s+1,t+1,&k);
        int ls=strlen(s+1),lt=strlen(t+1);
        reverse(s+1,s+ls+1); reverse(t+1,t+lt+1);
        if (ls<=lt)
        {
            for (i=ls+1;i<=lt;++i) s[i]='0'; ls=lt;
        } else
        {
            for (i=lt+1;i<=ls;++i) t[i]='0';
        }
        for (i=1;i<=ls;++i) s[i]=(s[i]!=t[i]?'1':'0');
        int zero=0; for (i=1;i<=ls;++i) if (s[i]=='1') break; else ++zero;
        if (zero!=ls&&zero/2+1<k) { puts("-1"); continue; }
        if (ls%2==1) s[++ls]='0';
        int ans=0; for (i=1;i<=ls;i+=2)
        if (s[i]=='1'&&s[i+1]=='0') (ans+=pw[i/2])%=mod; else
        if (s[i]=='1'&&s[i+1]=='1') (ans+=2LL*pw[i/2]%mod)%=mod; else
        if (s[i]=='0'&&s[i+1]=='1') (ans+=3LL*pw[i/2]%mod)%=mod;
        (ans+=1LL*(quick_pow(4,k)-4+mod)%mod*quick_pow(3)%mod)%=mod;
        printf("%d\n",ans);
    }
    return 0;
}

Postscript

唉这周连着VP的两场澳门和沈阳都激情银首(罚时输麻麻)

在加上之前VP的杭州和济南的金尾以及合肥现场的金尾(罚时赢麻麻),感觉我们队就是标准的金银分界线水平的队伍的说

感觉接下来其实就是要想办法减少失误之类的,因为每次比赛感觉都是会写能写的题很多,但机时严重不够,最后一总结发现又是因为有人犯病了导致调试浪费了大量时间

posted @ 2024-03-03 16:09  空気力学の詩  阅读(150)  评论(0编辑  收藏  举报