The 2023 ICPC Asia Regionals Online Contest (1)

Preface

这场打的还行,因为学长们都没发挥好被我们队偷了,但感觉发挥的也一般

前期开题顺序有点问题导致罚时很高,不过中期写题还是很顺的基本都是一遍过

只不过在3h的时候过完F到达8题后就开始坐牢了,虽然这场有两个字符串但徐神把H想复杂了,B可以说前面的建SAM和反串的AC自动机都想到了,也转化成了子树的交问题,但就是不知道怎么用线段树合并解决

然后C场上提出一个\(O(q\sqrt n)\)的做法,感觉出题人开\(5\times 10^5\)就是像卡根号的,所以就没敢开,剩下一个E高阶数论题做不来一点,弃了弃了

最后就是我和祁神在边上聊天刷榜,徐神在H题坐牢,划水划着划着就结束了

这场打完的教训就是对于一些高阶的知识点我们队可能没人会或者只有一个人会,这就导致后期大家很难交流,本来我们队就是以交流和配合见长的

因此以后可能我要去精进下字符串的科技,争取能做到跟上徐神的节奏,同时也要提升下全队的数论水平的说


A Qualifiers Ranking Rules

签到题,按题意模拟下即可,注意合理使用STL可以大幅简化代码复杂度

值得一提的是这道题中讲了合并排名的规则,感觉下场需要学长们发力打个高点的排名的说,不然等下学校要少一个名额了

#include<cstdio>
#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<utility>
#include<set>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
int n,m,rk; set <string> s1,s2; string s; map <string,pi> rst;
vector <pair <pi,string>> ans;
int main()
{
	//freopen("A.in","r",stdin); freopen("A.out","w",stdout);
	RI i; for (cin>>n>>m,rk=0,i=1;i<=n;++i)
	{
		cin>>s; if (s1.count(s)) continue; ++rk;
		if (!rst.count(s)) rst[s]=pi(rk,1);
		else rst[s]=min(rst[s],pi(rk,1));
		s1.insert(s);
	}
	for (rk=0,i=1;i<=m;++i)
	{
		cin>>s; if (s2.count(s)) continue; ++rk;
		if (!rst.count(s)) rst[s]=pi(rk,2);
		else rst[s]=min(rst[s],pi(rk,2));
		s2.insert(s);
	}
	for (auto [s,rk]:rst) ans.push_back(make_pair(rk,s));
	sort(ans.begin(),ans.end());
	for (auto [_,s]:ans) cout<<s<<endl;
	return 0;
}

B String

不会,留着以后再补,思路其实不难就是不好写,徐神秒了但卡在DS部分了,那就是我背锅


C Multiply Then Plus

本场最难的一题,正解是离线后对时间线段树分治,这里就先不表,讲一下我们的单根号的做法

首先这题的经典trick就是建下凸壳,但加入了单点修改和区间询问,考虑用分块解决

我们先对于每个块维护出块内所有直线构成的下凸壳,这样询问是很trivial的就是暴力遍历散块,然后在整块内查询

然后修改的话由于是单点的,可以直接暴力重构整个块对应的凸包

但如果直接这样做的话询问和修改都是\(\sqrt n\log n\)的,显然非常爆炸

首先我们发现修改时对于每个块不需要重新给直线排序,因为每次修改相当于删除有序序列中的一个元素,并加入一个新的元素,只需要\(O(\sqrt n)\)的插入时间即可,而建立凸壳的复杂度也是线性的

而询问的话可以先惰性地存下对于一个块的所有询问,在每次修改时一并处理累积的询问

可以给询问按照\(k\)排序后,在凸壳上单调移动最优决策点即可,可以省下二分的\(\log\),使得这部分的复杂度来到均摊\(O(1)\)

这样总复杂度就是\(O(q\sqrt n)\)了,但由于出题人特意构造了卡根号算法的数据因此在PTA上无法通过

以下代码经过对拍可以保证正确性,在随机的极限数据下开O2本机跑4s,等下次题目上了CF再去试试吧

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=500005,M=710;
const LL INF=1e18;
class FileInputOutput
{
    private:
        static const int S=1<<21;
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
        #define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
        char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[30];
    public:
        inline FileInputOutput(void) { Ftop=Fout; Fend=Fout+S; }
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        Tp inline void write(T x)
        {
            RI ptop=0; while (pt[++ptop]=x%10,x/=10);
            while (ptop) pc(pt[ptop--]+48); pc('\n');
        }
        inline void flush(void)
        {
            fwrite(Fout,1,Ftop-Fout,stdout);
        }
        #undef tc
        #undef pc
}F;
struct Line
{
	int k,b;
	inline Line(CI K=0,CI B=0)
	{
		k=K; b=B;
	}
	friend inline bool operator < (const Line& A,const Line& B)
	{
		return A.k!=B.k?A.k<B.k:A.b<B.b;
	}
	inline LL get(CI x)
	{
		return 1LL*k*x+b;
	}
};
inline double get_x(const Line& A,const Line& B)
{
	if (A.k==B.k) return A.b>B.b?-INF:INF;
	return 1.0*(B.b-A.b)/(A.k-B.k);
}
struct Convex
{
	vector <Line> stk; vector <double> p; int top;
	inline void init(void)
	{
		stk.clear(); p.clear(); top=0;
	}
	inline void insert(const Line& L)
	{
		if (stk.empty()) return stk.push_back(L),p.push_back(-INF);
		while (stk.size()>1&&get_x(L,stk.back())<p.back()) stk.pop_back(),p.pop_back();
		p.push_back(get_x(L,stk.back())); stk.push_back(L);
	}
	inline LL query(CI x)
	{
		while (top+1<p.size()&&p[top+1]<=x) ++top; return stk[top].get(x);
	}
}C[710]; int n,q,a[N],b[N],bel[N],opt,x,y,z; LL ans[N];
vector <Line> L[710]; vector <pi> ques[710];
inline void reset(CI cur)
{
	sort(ques[cur].begin(),ques[cur].end());
	for (auto [X,id]:ques[cur])
	ans[id]=max(ans[id],C[cur].query(X));
	ques[cur].clear(); 
}
inline LL BF(CI l,CI r,CI x)
{
	LL ret=0; for (RI i=l;i<=r;++i) ret=max(ret,1LL*a[i]*x+b[i]); return ret;
}
int main()
{
	//freopen("C.in","r",stdin); freopen("C.out","w",stdout);
	RI i,j; for (F.read(n),F.read(q),i=1;i<=n;++i)
	F.read(a[i]),F.read(b[i]),bel[i]=(i-1)/M+1,L[bel[i]].push_back(Line(a[i],b[i]));
	for (i=1;i<=bel[n];++i)
	{
		sort(L[i].begin(),L[i].end()); C[i].init();
		for (auto it:L[i]) C[i].insert(it);
	}
	for (memset(ans,-1,sizeof(ans)),i=1;i<=q;++i)
	{
		F.read(opt); F.read(x); F.read(y); F.read(z);
		if (opt==1)
		{
			reset(bel[x]); Line now=Line(y,z); vector <Line> tmp;
			bool flag=0; for (auto it:L[bel[x]])
			{
				if (it.k==a[x]&&it.b==b[x]) continue;
				if (flag||it<now) tmp.push_back(it);
				else flag=1,tmp.push_back(now),tmp.push_back(it);
			}
			if (!flag) tmp.push_back(now);
			a[x]=y; b[x]=z; C[bel[x]].init(); L[bel[x]]=tmp;
			for (auto it:tmp) C[bel[x]].insert(it);
		} else
		{
			if (bel[y]==bel[z]) { ans[i]=BF(y,z,x); continue; }
			ans[i]=max(BF(y,bel[y]*M,x),BF((bel[z]-1)*M+1,z,x));
			for (j=bel[y]+1;j<bel[z];++j) ques[j].push_back(pi(x,i));
		}
	}
	for (i=1;i<=bel[n];++i) reset(i);
	for (i=1;i<=q;++i) if (~ans[i]) F.write(ans[i]);
	return F.flush(),0;
}

D Transitivity

签到题,对于每个联通块显然我们都要把它补成一个团,统计下这样需要的边数

如果此时答案不为\(0\)就直接输出即可,否则说明此时的图由若干个团构成,那么找两个大小最小的团,用它们大小之积的边连通这两个团即可

比赛的时候由于脑抽了把找最小写成找最大WA了一发,罪过罪过

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int n,m,x[N],y[N],fa[N],sz[N],num[N]; long long ans;
inline int getfa(CI x)
{
	return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
	//freopen("D.in","r",stdin); freopen("D.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) fa[i]=i;
	for (i=1;i<=m;++i) scanf("%d%d",&x[i],&y[i]),fa[getfa(x[i])]=getfa(y[i]);
	for (i=1;i<=n;++i) ++sz[getfa(i)];
	for (i=1;i<=m;++i) ++num[getfa(x[i])];
	for (i=1;i<=n;++i) ans+=1LL*sz[i]*(sz[i]-1)/2LL-num[i];
	if (ans>0) return printf("%lld",ans),0;
	int mi=n,smi=n; for (i=1;i<=n;++i)
    {
        if (!sz[i]) continue;
	    if (sz[i]<mi) smi=mi,mi=sz[i]; else if (sz[i]<smi) smi=sz[i];
    }
	return printf("%lld",1LL*mi*smi),0;
}

E Magical Pair

高阶数论题,做不来的说,昨天和祁神交流了下发现根本搞不懂题解在写什么\kel


F Alice and Bob

只能说我自我定位清晰,知道自己没有思考博弈性质的能力,在空机后果断上去打表,然后全队一起找规律分析后得到了性质,最后随便套个数据结构就过了

首先写暴力也有些trick,注意到我们可以把所有数都减去最小的那个,并升序排序这些数作为状态,转移的话就是很朴素的博弈过程

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int f[N][N];
inline int DP(CI x,CI y)
{
	if (y<=1) return 0; if (~f[x][y]) return f[x][y];
	RI i; for (i=1;i<=x-i;++i)
	{
		int c[3]={i,x-i,y};	sort(c,c+3);
		if (!DP(c[1]-c[0],c[2]-c[0])) return f[x][y]=1;
	}
	for (i=1;i<=y-i;++i)
	{
		int c[3]={i,x,y-i}; sort(c,c+3);
		if (!DP(c[1]-c[0],c[2]-c[0])) return f[x][y]=1;
	}
	for (i=1;x+i<=y-i;++i)
	if (!DP(x+i,y-i)) return f[x][y]=1;
	return f[x][y]=0;
}
int main()
{
	freopen("BF.txt","w",stdout); memset(f,-1,sizeof(f));
	for (RI i=0;i<=50;++i) for (RI j=i;j<=50;++j)
	printf("0 %d %d :",i,j),puts(DP(i,j)?"Alice":"Bob");
	return 0;
}

然后对着打出来的表仔细观察会发现以下关键结论:

  • 当三个数不同时Alice必胜
  • 当其中两个数相同时的胜负情况很奇怪,一时半会找不到规律
  • 当三个数相同时Bob必胜

难点在于怎么处理形如\((0,0,x)\)这样的状态的胜负性,在大家相互扯皮的过程中祁神突然发现了关键在于\((0,0,x)\)变成\((0,\frac{x}{2},\frac{x}{2})\)一定是最优的

然后我就突然顿悟应该就是和\(x\)所含的\(2\)的幂次有关,然后一对比果然成立

因此这题的实现就是统计Bob获胜的局面数,先特判三个数相同的情况,然后考虑枚举有两个相同的那个数\(x\)

根据前面的结论我们会发现合法的\(y\)必须满足\(|x-y|\)有偶数个\(2\)(包括\(0\)个)作为约数,那么换句话说就是\(|x-y|\)的二进制下最低位的\(1\)后面的\(0\)的个数必须是偶数个

稍加观察会发现可以从低位到高位建立一棵01Trie,直接根据\(x\)的二进制表示就可以推出\(y\)对应的后缀形式,在Trie上查询即可

比赛的时候为了好实现写了两个\(\log\)的写法,没想到直接一发跑过了,本来想着T了再改成一个\(\log\)的说

#include<cstdio>
#include<iostream>
#include<unordered_map>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=500005;
int t,n,x,ans,rt; unordered_map <int,int> rst;
class Trie
{
	private:
		int tot,ch[N*65][2],c[N*65];
	public:
		inline void clear(void)
		{
			for (RI i=1;i<=tot;++i) ch[i][0]=ch[i][1]=c[i]=0; rt=tot=0;
		}
		inline void insert(int& now,CI x,CI d=0)
		{
			if (!now) now=++tot; ++c[now]; if (d==60) return; 
			insert(ch[now][(x>>d)&1LL],x,d+1);
		}
		inline int query(CI now,CI x,CI pre,CI d=0)
		{
			if (d>pre) return c[now]; if (!now) return 0;
			return query(ch[now][(x>>d)&1LL],x,pre,d+1);
		}
}T;
signed main()
{
	//freopen("F.in","r",stdin); freopen("F.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; scanf("%lld",&n); ans=n*(n-1)*(n-2)/6LL;
		for (rst.clear(),T.clear(),i=1;i<=n;++i)
		scanf("%lld",&x),T.insert(rt,x),++rst[x];
		for (auto [x,y]:rst) if (y>=2)
		{
			int ret=0,cur=0;
			for (i=0;i<60;++i)
			{
				int c=(x>>i)&1LL;
				if (!(i&1)) ret+=T.query(rt,cur|((c^1)<<i),i);
				cur|=(c<<i);
			}
			ans-=y*(y-1)/2LL*ret;
			if (y>=3) ans-=y*(y-1)*(y-2)/6LL;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

G Spanning Tree

ORZ徐神,这题本来不知道怎么实现的好,后面徐神教了我一种很好写复杂度也很优秀的做法,导致我们队没像包大爷它们队一样在这道题上卡一整场

这题的思路其实很简单,如果有解的话答案就是每次合并过程中两个联通块大小之积的倒数乘积,现在的难点在于如何判断给出的方案是否合法

可以先对目标的树进行一些处理,不妨将其定为有根树并定下每个点的父亲

每次合并联通块时,我们找到两个联通块中各自的LCA,记为\(x,y\),同时令\(x\)在目标树中的深度大于\(y\)

\(z\)为目标树中\(x\)的父亲,则我们只需要检验\(z\)是否在\(y\)所在的联通块内即可,这个做法的正确性显然

当然实现的时候我们不需要真的去写LCA,在并查集合并的过程中,每个联通块的LCA就是块内深度最小的点,只要在每次merge的时候维护一下即可

复杂度\(O(n\times \alpha(n))\)

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5,mod=998244353;
int n,a[N],b[N],x,y,fa[N],sz[N],lca[N],dep[N],anc[N],ans=1; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
	dep[now]=dep[fa]+1; anc[now]=fa;
	for (auto to:v[now]) if (to!=fa) DFS(to,now);
}
inline int getfa(CI x)
{
	return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
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()
{
	freopen("G.in","r",stdin); freopen("G.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<n;++i) scanf("%d%d",&a[i],&b[i]);
	for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for (i=1;i<=n;++i) fa[i]=lca[i]=i,sz[i]=1;
	for (DFS(),i=1;i<n;++i)
	{
		x=getfa(a[i]); y=getfa(b[i]);
		if (dep[lca[x]]<dep[lca[y]]) swap(x,y);
		if (getfa(anc[lca[x]])!=y) { ans=0; break; }
		ans=1LL*ans*quick_pow(1LL*sz[x]*sz[y]%mod)%mod;
		fa[x]=y; sz[y]+=sz[x];
	}
	return printf("%d",ans),0;
}

H Range Periodicity Query

怎么又是二分+Hash题啊,上次CCPC网络赛的A也是这样,徐神想了半天的其它字符串算法无果后,我去看了眼发现可以Hash就直接上了

但这次后半程划水严重,说实话到比赛结束我都没仔细看过H题题意,只听了徐神好几个版本的做法

这题首先可以发现如果\(p\)不是\(S_k\)的周期的话,它也一定不可能是\(S_t(t>k)\)的周期

因此我们可以先二分找到每个周期\(p\)对应的最大的版本号\(t(p)\),具体实现的话会发现由于每个版本的串都是\(S_n\)的一个子串

而根据周期和border的等价转换我们可以把问题变成检验border长度是否合法,只要用Hash判断一下即可

求出\(t(p)\)后对于每个询问\((k,l,r)\),我们要找的相当于是区间中最小的\(p\),且满足\(t(p)\ge k\)

可以把所有询问从大到小排序,并把所有周期按\(t\)值从大到小排序,two pointers加入每个周期即可,并用线段树实现单调修改和区间取\(\min\)

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

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef long long LL;
const int N=500005,INF=1e9;
const int mod1=998244353,mod2=1e9+7;
struct Hasher
{
	int x,y;
	inline Hasher(CI X=0,CI Y=0)
	{
		x=X; y=Y;
	}
	inline LL get_val(void)
	{
		return ((1LL*x)<<31LL)|(1LL*y);
	}
	friend inline bool operator == (const Hasher& A,const Hasher& B)
	{
		return A.x==B.x&&A.y==B.y;
	}
	friend inline Hasher operator + (const Hasher& A,const Hasher& B)
	{
		return Hasher((A.x+B.x)%mod1,(A.y+B.y)%mod2);
	}
	friend inline Hasher operator - (const Hasher& A,const Hasher& B)
	{
		return Hasher((A.x-B.x+mod1)%mod1,(A.y-B.y+mod2)%mod2);
	}
	friend inline Hasher operator * (const Hasher& A,const Hasher& B)
	{
		return Hasher(1LL*A.x*B.x%mod1,1LL*A.y*B.y%mod2);
	}
}h[N],pw[N]; deque <char> dq; char s[N]; int n,m,qq,d[N],l[N],r[N],ans[N];
const Hasher seed=Hasher(31,131);
inline Hasher get(CI l,CI r)
{
	return h[r]-h[l-1]*pw[r-l+1];
}
struct ifo
{
	int p,t,id;
	inline ifo(CI P=0,CI T=0,CI ID=0)
	{
		p=P; t=T; id=ID;
	}
	friend inline bool operator < (const ifo& A,const ifo& B)
	{
		return A.t>B.t;
	}
}o[N];
struct ques
{
	int k,l,r,id;
	inline ques(CI K=0,CI L=0,CI R=0,CI ID=0)
	{
		k=K; l=L; r=R; id=ID;
	}
	friend inline bool operator < (const ques& A,const ques& B)
	{
		return A.k>B.k;
	}
}q[N];
class Segment_Tree
{
	private:
		int mi[N<<2];
		inline void pushup(CI now)
		{
			mi[now]=min(mi[now<<1],mi[now<<1|1]);
		}
	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 build(TN)
		{
			mi[now]=INF; if (l==r) return; int mid=l+r>>1; build(LS); build(RS);
		}
		inline void updata(CI pos,CI mv,TN)
		{
			if (l==r) return (void)(mi[now]=mv); int mid=l+r>>1;
			if (pos<=mid) updata(pos,mv,LS); else updata(pos,mv,RS); pushup(now);
		}
		inline int query(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return mi[now]; int mid=l+r>>1,ret=INF;
			if (beg<=mid) ret=min(ret,query(beg,end,LS)); if (end>mid) ret=min(ret,query(beg,end,RS)); return ret;
		}
		#undef TN
		#undef LS
		#undef RS
}SEG;
int main()
{
	//freopen("H.in","r",stdin); freopen("H.out","w",stdout);
	RI i,j; for (scanf("%d%s",&n,s+1),i=1;i<=n;++i)
	if ('A'<=s[i]&&s[i]<='Z') dq.push_back(s[i]-'A'+'a');
	else dq.push_front(s[i]),d[i-1]=1;
	for (i=n;i>=1;--i) d[i]+=d[i+1],l[i]=1+d[i],r[i]=i+d[i];
	//for (i=1;i<=n;++i) printf("%d %d\n",l[i],r[i]);
	for (i=1;i<=n;++i) s[i]=dq.front(),dq.pop_front();
	for (pw[0]=Hasher(1,1),i=1;i<=n;++i) pw[i]=pw[i-1]*seed;
	for (i=1;i<=n;++i) h[i]=h[i-1]*seed+Hasher(s[i],s[i]);
	for (scanf("%d",&m),i=1;i<=m;++i)
	{
		scanf("%d",&o[i].p); int L=o[i].p,R=n,mid,ret;
		while (L<=R)
		{
			mid=L+R>>1; int len=mid-o[i].p;
			if (get(l[mid],l[mid]+len-1)==get(r[mid]-len+1,r[mid]))
			ret=mid,L=mid+1; else R=mid-1;
		}
		o[i].t=ret; o[i].id=i;
		//printf("%d %d\n",o[i].p,o[i].t);
	}
	for (scanf("%d",&qq),i=1;i<=qq;++i)
	scanf("%d%d%d",&q[i].k,&q[i].l,&q[i].r),q[i].id=i;
	sort(o+1,o+m+1); sort(q+1,q+qq+1); SEG.build();
	for (i=j=1;i<=qq;++i)
	{
		while (j<=m&&o[j].t>=q[i].k) SEG.updata(o[j].id,o[j].p),++j;
		int tmp=SEG.query(q[i].l,q[i].r); ans[q[i].id]=tmp<=q[i].k?tmp:-1;
	}
	for (i=1;i<=qq;++i) printf("%d\n",ans[i]);
	return 0;
}

I Pa?sWorD

徐神开场写的,我题目都没看过来着,好像是个DP的优化题,看过的人挺多的就不管了

#include <bits/stdc++.h>

int readInChar;

using llsi = long long signed int;

const llsi mod = 998244353;

inline int readi() {
	int &c = readInChar, res = 0;
	while(c > '9' || c < '0') c = getchar();
	while('0' <= c && c <= '9')
		res = res * 10 + (c ^ 48),
		c = getchar();
	return res;
}

inline int myGetchar() {
	int &c = readInChar; c = getchar();
	while(c == '\r' || c == '\n' || c == ' ') c = getchar();
	return c;
}

int n;

enum {
	UPPER = 1,
	LOWER = 2,
	DIGIT = 4
};

llsi dp_base[2][8][128], ss[8];
auto dp0 = dp_base[0], dp1 = dp_base[1];

void update(int c) {
	int upd;
	if('0' <= c && c <= '9') upd = DIGIT;
	if('a' <= c && c <= 'z') upd = LOWER;
	if('A' <= c && c <= 'Z') upd = UPPER;
	for(int i = 0; i < 8; ++i) {
		dp1[i | upd][c] += ss[i] + mod - dp0[i][c];
	}
	return ;
}

int main() {
	//freopen("1.in", "r", stdin);
	n = readi();
	dp0[0][0] = 1;
	while(n--) {
		char c = myGetchar();
		memset(dp1, 0, sizeof(dp1));
		memset(ss, 0, sizeof(ss));
		for(int i = 0; i < 8; ++i) for(int j = 0; j <= 'z'; ++j)
			ss[i] += dp0[i][j] %= mod;
		for(int i = 0; i < 8; ++i) ss[i] %= mod;
		if(c == '?') {
			for(int i = 'a'; i <= 'z'; ++i) update(i);
			for(int i = 'A'; i <= 'Z'; ++i) update(i);
			for(int i = '0'; i <= '9'; ++i) update(i);
		} else if('a' <= c && c <= 'z') {
			update(c);
			update(c + 'A' - 'a');
		} else update(c);
		std::swap(dp0, dp1);
	}
	llsi ans = 0;
	for(int i = 0; i < 128; ++i) ans += dp0[7][i] % mod;
	printf("%lld\n", ans % mod);
	return 0;
}

J Minimum Manhattan Distance

祁神稍作思考发现答案一定在圆\(45^\circ\)方向上的四个点上,把这四个点的答案取\(\min\)即可

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

struct Pt{
	LD x, y;	
	Pt operator+(const Pt b)const{return Pt{x+b.x, y+b.y};}
	Pt operator-(const Pt b)const{return Pt{x-b.x, y-b.y};}
	Pt operator*(const LD b)const{return Pt{x*b, y*b};}
	LD len(){return sqrt(x*x+y*y);}
};

int t;
const int dir[4][2] = {{1, -1}, {1, 1}, {-1, 1}, {-1, -1}};
const LD rt2 = sqrtl(2.0L)*0.5L;

LD manhadun(Pt a, Pt b){
	return fabs(a.x-b.x)+fabs(a.y-b.y);
}

int main(){
	//printf("%Lf\n", rt2);
	scanf("%d", &t);
	while (t--){
		int a, b, c, d;
		Pt p1, p2, p3, p4;
		scanf("%d%d%d%d", &a, &b, &c, &d);
		p3.x=a, p3.y=b, p4.x=c, p4.y=d;
		scanf("%d%d%d%d", &a, &b, &c, &d);
		p1.x=a, p1.y=b, p2.x=c, p2.y=d;
		Pt C1=(p1+p2)*0.5L, C2=(p3+p4)*0.5L;
		//printf("(%Lf %Lf)\n", C1.x, C1.y);
		LD R = (p1-p2).len()*0.5L;
		Pt tmp[4];
		for (int i=0; i<4; ++i){
			tmp[i]=C1;
			tmp[i].x += dir[i][0]*R*rt2;
			tmp[i].y += dir[i][1]*R*rt2;
			//printf("(%Lf %Lf)\n", tmp[i].x, tmp[i].y);
		}
		LD ans = 1e9;
		for (int i=0; i<4; ++i){
			ans = min(ans, manhadun(tmp[i], C2));
		}
		printf("%.9Lf\n", ans);
	}
	return 0;	
}

K Minimum Euclidean Distance

小清新计算几何题,徐神推了积分的式子然后祁神上去无板光速写了代码一发入魂,只能说这种难度的几何题对祁神来说就是洒洒水

以圆心为原点建立极坐标系,很容易通过极坐标积分算出答案是\(\frac{r^2}{2}+a^2\),其中\(r\)为圆的半径,\(a\)为答案点到圆心的距离

那么问题转化为求凸包内一点到某个点的最近距离,先判断圆心是否在凸包内(叉积是否在每条边的同一侧)

否则计算圆心到每一条边的距离即可,这个就是做垂线看是否在线段上,可以用点积和叉积来做到无精度误差

(这题论思路和代码难度都不难,但祁神不看板子光速写完一遍过就直接震撼我,感觉几何会成为我们队的一大优势点了这样看来)

#include<bits/stdc++.h>
#define int long long
using namespace std;
#define LD long double
const LD eps = 1e-8;

int sgn(LD x){return fabs(x)<eps ? 0 : (x>eps ? 1 : -1);}
struct Pt{
	LD x, y;	
	LD cross(Pt b)const{return x*b.y-y*b.x;}
	LD dot(Pt b)const{return x*b.x+y*b.y;}
	Pt operator+(const Pt b)const{return Pt{x+b.x, y+b.y};}
	Pt operator-(const Pt b)const{return Pt{x-b.x, y-b.y};}
	Pt operator*(const LD b)const{return Pt{x*b, y*b};}
	LD len(){return sqrt(x*x+y*y);}
};

const int N = 5e3+5;
int n, q;
Pt pt[N];

bool inConvh(Pt p){
	for (int i=0; i<n; ++i){
		if (sgn((pt[i+1]-pt[i]).cross(p-pt[i])) < 0) return false;	
	}
	return true;
}

LD dis(Pt p, Pt a, Pt b){
	if ((p-a).dot(b-a)>=0 && (p-b).dot(a-b)>=0){
		return fabs((b-a).cross(p-a) / (b-a).len());
	}else{
		return min((p-a).len(), (p-b).len());
	}
}

signed main(){
	//printf("%Lf\n", rt2);
	scanf("%lld%lld", &n, &q);
	for (int i=0; i<n; ++i){
		int a, b;
		scanf("%lld%lld", &a, &b);
		pt[i].x=a, pt[i].y=b;
	}
	pt[n]=pt[0];
	
	while (q--){
		int a, b, c, d;
		scanf("%lld%lld%lld%lld", &a, &b, &c, &d);
		Pt p1{a, b}, p2{c, d};
		Pt C{(a+c)*0.5L, (b+d)*0.5L};
		LD R = (p1-p2).len()*0.5L;
		LD ans = R*R*0.5L;
		if (inConvh(C)) printf("%Lf\n", ans);
		else{
			LD res=1e100;
			for (int i=0; i<n; ++i){
				res = min(res, dis(C, pt[i], pt[i+1]));
			}
			ans += res*res;
			printf("%.9Lf\n", ans);
		}
	}
	
	return 0;	
}

L KaChang!

签到题,祁神写的,我题目都没看

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

int n, T;
int main(){
	scanf("%d%d", &n, &T);
	int mx=0;
	for (int i=1; i<=n; ++i){
		int a;
		scanf("%d", &a);
		mx = max(mx, a);
	}
	printf("%d\n", max(2, (mx+T-1)/T));
	return 0;
}

Postscript

这周六还有一场网络赛,结束后下周就是国庆集训,区域赛要来了还是得拿点心出来的说

posted @ 2023-09-20 12:17  空気力学の詩  阅读(377)  评论(0编辑  收藏  举报