vj传送门https://vjudge.net/contest/630331
洛谷传送门https://www.luogu.com.cn/contest/172272
codeforces 传送门
https://codeforces.com/gym/105176
c++ 模板框架
#include<bits/stdc++.h>
#define rep(i,a,b) for (int i=a;i<b;++i)
#define per(i,a,b) for (int i=a;i>b;--i)
#define se second
#define fi first
#define endl '\n'
#define all(x) (x).begin(),(x).end()
#define pii pair<int,int>
#define pli pair<LL,int>
#define pll pair<LL,LL>
#define MEM(a,x) memset(a,x,sizeof(a))
#define OJBK {cout<<"ok"<<endl;return;}
#define lowbit(x) ((x)&-(x))
inline int Ls(int p){return p<<1;}
inline int Rs(int p){return p<<1|1;}
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=1e3+10;
inline void Solve()
{
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("test_1.in","r",stdin);
freopen("test_1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int _=1;
//cin>>_;
while(_--){
Solve();
}
return 0;
}
python模板
import sys
def Solve():
pass
if __name__ == '__main__':
sys.stdin = open(r"1.txt", "r")
sys.stdout = open(r"1.out", "w")
t=1
#t=int(input())
while t:
Solve()
t-=1
A - 交小西的礼物
签到,按题意直接输出。
a,b,c,d=map(int,input().split())
print(a+b+2*c+3*d)
B - 转呀转
旋转方向并不会影响最后位移结果,由于转速单位是圈/s,角度值就可以直接用2pi*圈数直接算出来,用点的旋转公式求出旋转后的点坐标,最后求两点距离即可。
旋转变换公式:
from math import sqrt,pi,sin,cos
def dist(a,b,c,d):
return sqrt((a-c)**2+(b-d)**2)
def Solve():
x,y=map(int,input().split())
t,v=float(input()),float(input())
th=2*pi*t*v
nx,ny=x*cos(th)-y*sin(th),x*sin(th)+y*cos(th)
res=dist(x,y,nx,ny)
print('{:.7f}'.format(res))
if __name__ == '__main__':
t=1
#t=int(input())
while t:
Solve()
t-=1
C - 榕树之心
按照题目给出的公式转换即可。
from math import sqrt
def chg(x,y):
tt=sqrt(3)*0.5
nx=0.5*x+0.5*y
ny=tt*x-tt*y
return (nx,ny)
def Solve():
x,y=map(int,input().split())
nx,ny=chg(x,y)
print('{:.7f} {:.7f}'.format(nx,ny))
if __name__ == '__main__':
t=1
#t=int(input())
while t:
Solve()
t-=1
D - 瑟莉姆的宴会
考虑一种特殊的树:选择某一个节点,让它作为其他所有节点的直接支配者(类似于星型网络拓扑结构)。这种结构消除了支配关系转递性,只需要考虑选择哪个节点即可。节点选择只需要满足支配条件数对中该节点左边出现次数大于等于右边次数即可。
const int N=1e5+10;
int l[N],r[N];
inline void Solve()
{
int n,m;
cin>>n>>m;
while(m--){
int x,y;
cin>>x>>y;
++l[x],++r[y];
}
int pos=0;
rep(i,1,n+1) if(l[i]>=r[i]){
pos=i;
break;
}
rep(i,1,n+1){
if(i==pos) cout<<0<<" ";
else cout<<pos<<" ";
}cout<<endl;
}
E - 雪中楼
问题就是维护一个递增序列,可以把 0 作为最小的数,每一次操作就是把 i 插入到 的后面,用双向链表就可以模拟这个过程。
const int N=2e5+10;
int n,l[N],r[N];
inline void Solve()
{
int n,x;cin>>n;
r[0]=n+1;l[n+1]=0;
rep(i,1,n+1){
cin>>x;
r[i]=r[x];l[i]=x;
l[r[x]]=i;r[x]=i;
}
for(int i=r[0];i!=n+1;i=r[i]){
cout<<i<<" ";
}cout<<endl;
}
F - Everyone's ALL IN
不难发现骑士团 x 和 y 比试最大收益就是 x 中的编号总和 y 中的编号总和相乘。维护出每一个骑士团总和即可。
const int N=2e5+10,M=1e6+10;
LL mp[M];
inline void Solve()
{
int n,m,x;
cin>>n>>m;
rep(i,1,n+1) cin>>x,mp[x]+=i;
while(m--){
int a,b;
cin>>a>>b;
cout<<mp[a]*mp[b]<<endl;
}
}
G - 循环移位
把数据看做二进制,考虑 dp。用 表示把数组往前循环移动 i 位,二进制第 j 位的运算总和。暴力的情况下至少需要枚举
次,然而注意到二进制循环的性质,在计算第 j 位的时候只需要计算出前
次移动范围,后面都是重复的,即 i 只需要枚举
。(例如考虑计算 8 个数的第一位,前 8 个数二进制是 000,001,010,011,100,101,110,111,显然第一位就是
次一个循环)。
状态可以通过
来计算,在前一个状态的基础上暴力枚举状态发生改变的点,注意到从
是
第 j 位对应 0(开始全 0),到了
中
第 j 位对应 1 (最后一个全1),然后发现第 j 位循环是
,只需要枚举
次更新这些点即可。
(例如用 更新
时就只需要枚举如下标红的位)
综上,时间复杂度可以控制在
const int N=(1<<20)+10;
int a[N],f[3][N][20];
int sf_mod(int x,int m=MOD)
{
return (x%m+m)%m;
}
void Solve()
{
int n;
cin>>n;
int m=1<<n;
rep(i,0,m) cin>>a[i];
rep(j,0,n){
rep(i,0,m){
f[0][0][j]+=(a[i]>>j&1)^(i>>j&1);
f[1][0][j]+=(a[i]>>j&1)&(i>>j&1);
f[2][0][j]+=(a[i]>>j&1)|(i>>j&1);
}
int pj=(1<<j);
rep(i,1,2*pj){
rep(_,0,3) f[_][i][j]=f[_][i-1][j];
for(int k=(i-1)%pj;k<m;k+=pj){
int x=sf_mod(k-i+1,m),y=sf_mod(k-i,m);
f[0][i][j]-=(a[k]>>j&1)^(x>>j&1),f[0][i][j]+=(a[k]>>j&1)^(y>>j&1);
f[1][i][j]-=(a[k]>>j&1)&(x>>j&1),f[1][i][j]+=(a[k]>>j&1)&(y>>j&1);
f[2][i][j]-=(a[k]>>j&1)|(x>>j&1),f[2][i][j]+=(a[k]>>j&1)|(y>>j&1);
}
}
}
LL c[3]={0};
rep(_,0,3) rep(i,0,m){
LL res=0;
rep(j,0,n) res+=1ll*f[_][i%(1<<j+1)][j]*(1<<j);
c[_]=max(c[_],res);
}
cout<<c[0]<<" "<<c[1]<<" "<<c[2]<<endl;
}
H - 图上操作
因为边权很小,考虑枚举边值来更新答案。用 表示保留原来图中边值
构成的子图,注意到如果从小到大来枚举该子图,每一次对子图做一次搜索,很显然如果当前子图中点 u 能从 1 到达,i 表示了当前子图状态下所有从 1 到达 u 的最小值,就可以用当前的值 i 来更新点 u 的答案。
下面的例子可以帮助理解:
把整个操作变成逆向增加权值操作,原理同上,只需要把增加权值的对应子图上加边,再从小到大对子图做搜索更新答案即可。
const int MOD=998244353;
const int N=1e5+10,M=2e5+10;
int n,m,q,res,mx[N];
int p2[N],ans[M];
struct Edge{
int s,t,d;
}eg[M];
struct Query{
int x,y;
}qy[M];
struct Gragh{
int h[N],e[M],ne[M],idx=0;
bool st[N];
void add(int a,int b){
e[++idx]=b;ne[idx]=h[a];h[a]=idx;
}
}g[105];
int sf_mod(int x,int mod=MOD)
{
return (x%mod+mod)%mod;
}
void dfs(int x,int u)
{
g[x].st[u]=1;
if(x>mx[u]&&u!=1){
res=(res+1ll*p2[u]*x%MOD)%MOD;
res=(res-1ll*p2[u]*mx[u]%MOD)%MOD;
res=sf_mod(res);
mx[u]=x;
}
for(int i=g[x].h[u];i;i=g[x].ne[i]){
int v=g[x].e[i];
if(g[x].st[v]) continue;
dfs(x,v);
}
}
void Solve()
{
cin>>n>>m>>q;
rep(i,1,m+1) cin>>eg[i].s>>eg[i].t>>eg[i].d;
rep(i,1,q+1){
int x,y;
cin>>x>>y;
qy[i]={x,y}; eg[x].d-=y;
}
p2[0]=1;
rep(i,1,n+1) p2[i]=1ll*p2[i-1]*2%MOD;
rep(i,1,m+1) rep(j,1,eg[i].d+1) g[j].add(eg[i].s,eg[i].t);
rep(i,1,101) dfs(i,1);
per(i,q,0){
ans[i]=res;
int x=qy[i].x,y=qy[i].y;
rep(j,eg[x].d+1,eg[x].d+y+1){
g[j].add(eg[x].s,eg[x].t);
if(g[j].st[eg[x].s]) dfs(j,eg[x].s);
}
eg[x].d+=y;
}
rep(i,1,q+1) cout<<ans[i]<<endl;
}
I - 命令行
很不错的一道模拟题,和实际运用结合。
先把字符串插入到 trie 树中,下面的操作我们只需要维护一个p,表示当前匹配到字符串在trie树中的节点编号。用一个字符串 s 来维护当前字符匹配失败的部分。
添加一个字符c:如果 s 非空或者 p 没有对应c的子节点,则直接把 c 加在 s 后面;否则把 p 指向子节点。
删除一个字符:如果 s 非空直接删除最后一位,否则把 p 跳到父节点。
回车:如果 s 非空说明还有不匹配字符,直接返回 -1 ;否则根据 p 节点对应字符编号判断。
补全:维护一个 jmp[p],表示从 p 开始的最长前缀能走到的最远节点。从 p 开始,往后每一步要保证子节点是唯一的,否则违反公共前缀定义,而且如果走到了某一个字符串匹配完了也要停止。找到最远的节点后,把中途的节点 jmp 值都回填成该节点(类似编译原理中回填技术的思想)。
const int N=1e6+10,M=1e6+10;
int ne[N][26],cnt,idx[N],pre[N],jmp[N];
inline void Add(string &s,int &p,char c)
{
int x=c-'a';
if(s.size()||!ne[p][x]){
s+=c;
}else{
p=ne[p][x];
}
}
inline void Del(string &s,int &p)
{
if(!s.empty()){
s.pop_back();
}else{
p=pre[p];
}
}
inline void Ins(int u,string &s)
{
int p=0;
for(char c:s){
int x=c-'a';
if(!ne[p][x]) ne[p][x]=++cnt;
pre[ne[p][x]]=p;
p=ne[p][x];
}
idx[p]=u;
}
inline int Find(string &s,int &p)
{
if(s.size()) return -1;
if(idx[p]) return idx[p];
else return -1;
}
inline void Tab(string &s,int &p)
{
if(s.size()) return;
if(jmp[p]) p=jmp[p];
else{
vector<int>bkp; bkp.emplace_back(p);
while(!idx[p]){
int x=-1;bool ok=1;
rep(i,0,26) if(ne[p][i]){
if(x==-1) x=i;
else ok=0;
}
if(!ok||x==-1) break;
p=ne[p][x];
if(jmp[p]){
p=jmp[p]; break;
}
bkp.emplace_back(p);
}
for(int x:bkp) jmp[x]=p;
}
}
inline void Solve()
{
int n,m;
string s,nw;
cin>>n>>m;
rep(i,1,n+1){
cin>>s;
Ins(i,s);
}
cin>>s;
int p=0;
for(char c:s){
if(c=='B'){
Del(nw,p);
}else if(c=='T'){
Tab(nw,p);
}else if(c=='E'){
cout<<Find(nw,p)<<" ";
nw="";p=0;
}else{
Add(nw,p,c);
}
}
}
J - 最后一块石头的重量
考虑一种方案,先把数组分为 A,B 两个集合,当两个集合都不为空时,每一次分别从A,B中取出两个元素a,b,按照题设规定,若,将
加入到集合A中;若
,将
加入到集合B中。相等则不做任何操作。最后会发现,用
表示起初A集合所有元素之和,
表示起初B集合所有元素之和,这样计算的值就是
。而且本题最后的情况不外乎三种:
。于是可以把问题转换为集合划分问题,并计算
的最小值。
考虑dp,用表示在前 i 个当中,且最后答案为 j 是否合法。于是就有转移方程:
c++中可以使用 bitset 实现,计算分为三个部分:1. ,对应计算
的情况;2.
,计算
的情况;3.
,计算
的情况。
空间大小最坏会达到,直接开又会爆空间,可以尝试打乱数组顺序防止被阴间数据卡掉。
const int N=2e6+10,M=1e4+10;
bitset<N>f[2];
int a[M];
void Solve()
{
int n; cin>>n;
rep(i,1,n+1) cin>>a[i];
random_shuffle(a+1,a+1+n);
f[0][0]=1;
rep(i,1,n+1){
f[i&1]=(f[~i&1]<<a[i])|(f[~i&1]>>a[i]);
per(j,a[i],-1) f[i&1][a[i]-j]=f[i&1][a[i]-j]|f[~i&1][j];
}
rep(i,0,5001)if(f[n&1][i]){
cout<<i<<endl;
break;
}
}
K - 崩坏:星穹铁道
用表示前 i 次行动,且最后战技点为 j 的方案数。
初始,再根据3种类型,可以很容易写出递推式,再写成矩阵的形式用快速幂即可。用
来表示三类转移方法:
其实可以发现第三种其实是前两种的叠加,有。
最后的答案就是:
#include<bits/stdc++.h>
#define rep(i,a,b) for (int i=a;i<b;++i)
#define per(i,a,b) for (int i=a;i>b;--i)
#define se second
#define fi first
#define endl '\n'
#define all(x) (x).begin(),(x).end()
#define pii pair<int,int>
#define pli pair<LL,int>
#define pll pair<LL,LL>
#define MEM(a,x) memset(a,x,sizeof(a))
#define OJBK {cout<<"ok"<<endl;return;}
inline int Ls(int p){return p<<1;}
inline int Rs(int p){return p<<1|1;}
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int MOD=998244353;
int a[5];
struct mat{
int a[6][6];
mat(){
MEM(a,0);
}
mat operator | (const mat p) const{
mat c;
rep(i,0,6) rep(j,0,6) c.a[i][j]=a[i][j]|p.a[i][j];
return c;
}
mat operator * (const mat p) const{
mat c;
rep(i,0,6) rep(j,0,6){
c.a[i][j]=0;
rep(k,0,6) c.a[i][j]=(c.a[i][j]+1ll*a[i][k]*p.a[k][j]%MOD)%MOD;
}
return c;
}
void output(){
rep(i,0,6){
rep(j,0,6) cout<<a[i][j]<<" ";
cout<<endl;
}
}
}A[4];
mat Qp(mat a,LL k)
{
mat res;
rep(i,0,6) res.a[i][i]=1;
while(k){
if(k&1) res=res*a;
a=a*a;
k>>=1;
}
return res;
}
void INIT()
{
rep(i,0,6){
if(i<5) A[1].a[i][i+1]=1;
else A[1].a[i][i]=1;
}
rep(i,0,6){
if(i) A[2].a[i][i-1]=1;
else A[2].a[0][1]=1;
}
A[3]=A[1]|A[2];
}
void Solve()
{
LL n;int k;
cin>>n>>k;
rep(i,0,4) cin>>a[i];
mat f0;f0.a[0][k]=1;
mat B,C;
rep(i,0,6) B.a[i][i]=C.a[i][i]=1;
rep(i,0,4) B=B*A[a[i]];
rep(i,0,n%4) C=C*A[a[i]];
mat res=f0*Qp(B,n/4)*C;
int ans=0;
rep(i,0,6) ans=(ans+res.a[0][i])%MOD;
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int _=1;
INIT();
//cin>>_;
while(_--){
Solve();
}
return 0;
}
L - 勘探队
因为行走的 x 坐标顺序是固定的,只要确定出每一次的 y 坐标让总代价达到最小即可,问题转换为求某一个约束条件下的最值问题,可以用拉格朗日乘数法解决。
注意最后的终点为 (0, y) ,放置完了所有装备后还要回到原点,为了方便设 。
数组存储第 i 个装备质量,用
数组表示
的前缀和数组。
求最值:
约束条件:
对 求导,可以得到:
整理一下:
注意到 时
和
是呈正相关的,于是可以二分出
的值,二分检查为约束条件,求出值后得到
代入原方程即可。
typedef double db;
const int N=1e4+10;
int a[N],b[N];
int n,m,y;
db calc(int i,db x)
{
return (db)(m+a[n]-a[i-1])*(m+a[n]-a[i-1])-x*x;
}
bool che(db x)
{
db res=0;
rep(i,1,n+2){
db sq2=calc(i,x);
if(sq2<0) return true;
res+=x*abs(b[i]-b[i-1])/sqrt(sq2);
}
return res>y;
}
void Solve()
{
cin>>n>>m>>y;
rep(i,1,n+1) cin>>a[i],a[i]+=a[i-1];
a[n+1]+=a[n];
rep(i,1,n+1) cin>>b[i];
db l=0,r=1e5; int lim=1e3;
rep(_,0,lim){
db mid=(l+r)/2;
if(che(mid)) r=mid;
else l=mid;
}
db ans=0;
rep(i,1,n+2){
db yi=l*abs(b[i]-b[i-1])/sqrt(calc(i,l));
ans+=(m+a[n]-a[i-1])*sqrt((b[i]-b[i-1])*(b[i]-b[i-1])+yi*yi);
}
cout<<fixed<<setprecision(9)<<ans<<endl;
}
M - 生命游戏
按照题意模拟即可。用 st[i] 表示 i 是否被删除,d[i] 来维护 i 节点的度数。初始先把所有度为 k 的待删节点加到队列中,每一次都把所有队列中的节点移除,同时更新度数,把新产生的度为 k 的节点再加到队列中,重复直到没有新的节点。
最后遍历所有节点,用标记+dfs的方法统计连通块个数即可。
const int N=1e6+10;
int n,k,d[N];
vector<int>e[N];
bool st[N];
void dfs(int u)
{
st[u]=1;
for(int v:e[u]) if(!st[v]) dfs(v);
}
inline void Solve()
{
cin>>n>>k;
rep(i,0,n-1){
int a,b;
cin>>a>>b;
e[a].emplace_back(b);e[b].emplace_back(a);
++d[a];++d[b];
}
queue<int>q;
rep(i,1,n+1) if(d[i]==k) q.push(i),st[i]=1;
while(q.size()){
vector<int>t;
while(q.size()){
int u=q.front();q.pop();
for(int v:e[u]){
if(st[v]) continue;
if(--d[v]==k) t.emplace_back(v);
}
}
for(int x:t) if(d[x]==k) q.push(x),st[x]=1;
}
int ans=0;
rep(i,1,n+1) if(!st[i]) ++ans,dfs(i);
cout<<ans<<endl;
}
N - 圣诞树
自底向上的遍历,如果某个节点为根的子树已经满足要求,则将其划分为一个联通块。
dfs+启发式合并(小集合并大集合)即可通过。
const int N=2e5+10,M=2*N;
int n,k;
int a[N];
int h[N],e[M],ne[M],idx,ans;
set<int>st[N];
void add(int a,int b)
{
e[++idx]=b,ne[idx]=h[a],h[a]=idx;
}
void dfs(int u,int fa)
{
st[u].insert(a[u]);
for(int i=h[u];i;i=ne[i]){
int v=e[i];
if(v==fa) continue;
dfs(v,u);
if(st[v].size()>st[u].size()) swap(st[u],st[v]);
for(auto it:st[v]) st[u].insert(it);
}
if(st[u].size()>=k){
++ans;
st[u].clear();
}
}
void Solve()
{
cin>>n>>k;
rep(i,1,n+1) cin>>a[i];
rep(i,0,n-1){
int x,y;
cin>>x>>y;
add(x,y);add(y,x);
}
dfs(1,0);
cout<<ans<<endl;
}
O - 筛法
算式化简:
后面的式子可以采用分块技术,由于涉及到欧拉函数求和,可以先维护一个欧拉函数的前缀和,方法就是记忆化搜索+分块,直接套用模板,这里不再赘述。
const int N=1e7;
unordered_map<int,LL>mp;
LL s[N+5];
void INIT()
{
s[1]=1;
rep(i,2,N+1) if(!s[i]){
for(int j=i;j<=N;j+=i){
if(!s[j]) s[j]=j;
s[j]=s[j]/i*(i-1);
}
}
rep(i,1,N+1) s[i]+=s[i-1];
}
LL F(int x)
{
if(x<=N) return s[x];
if(mp.count(x)) return mp[x];
LL res=1ll*x*(x+1)/2;
for(int i=2;i<=x;){
int r=x/(x/i);
res-=F(x/i)*(r-i+1);
i=r+1;
}
return mp[x]=res;
}
inline void Solve()
{
INIT();
int n;
cin>>n;
LL ans=0;
for(int i=2;i<=n;){
int r=n/(n/i);
ans+=n/i*(F(r)-F(i-1));
i=r+1;
}
cout<<n+ans*2<<endl;
}