随便说点
集训队为了备战省赛开了一场 V P VP VP,总的来说结果还是不错的,一个小时连写 5 5 5题直接就上了铜牌线,算是比较开心的吧。
A. League of Legends
题意
红蓝队伍每个队伍 5 5 5个人,每个人有血量,蓝队先攻击,每次攻击受击对象减少一点体力,请问哪个队伍会赢。
题解
两个队伍血量分别相加然后比大小,相等的情况下蓝队赢,不相等谁的血量高谁赢。
void MAIN(){
int sum1=0,sum2=0;
for(int i=1;i<=5;i++){
int a;
cin>>a;
sum1+=a;
}
for(int i=1;i<=5;i++){
int a;
cin>>a;
sum2+=a;
}
if(sum1>=sum2) cout<<"Blue"<<endl;
else cout<<"Red"<<endl;
return ;
}
C. Cube
题意
给出八个点的坐标,请问这八个点能否构成一个立方体。
题解
立方体有 8 8 8个点, 1 1 1个点向外引出与其他 7 7 7个点相连,如果每个点向外引出的 7 7 7条边中都有 3 3 3条边相互垂直相互相等证明这就是一个立方体。垂直可以用向量积为 0 0 0来判断。
void MAIN(){
for(int i=1;i<=8;i++) cin>>pir[i].first.first>>pir[i].first.second>>pir[i].second;
for(int i=1;i<=8;i++) for(int j=i+1;j<=8;j++) if(pir[i]==pir[j]){cout<<"NO"<<endl;return ;}
for(int i=1;i<=8;i++){
for(int j=i+1;j<=8;j++){
x[i][j]=x[j][i]=pir[i].first.first-pir[j].first.first;
y[i][j]=y[j][i]=pir[i].first.second-pir[j].first.second;
z[i][j]=z[j][i]=pir[i].second-pir[j].second;
}
}
int ans1=0;
for(int i=1;i<=8;i++){
for(int j=1;j<=8;j++){
if(i==j) continue;
l[i][j]=len(x[i][j],y[i][j],z[i][j]);
}
}
for(int i=1;i<=8;i++){
int cnt=0;
for(int j=1;j<=8;j++){
if(j==i) continue;
for(int k=j+1;k<=8;k++){
if(k==i) continue;
if(l[i][j]==l[i][k]) cnt++;
}
}
if(cnt==6) ans1++;
}
int ans2=0;
for(int i=1;i<=8;i++){
int cnt=0;
for(int j=1;j<=8;j++){
if(j==i) continue;
for(int k=j+1;k<=8;k++){
if(k==i) continue;
int sum=x[i][j]*x[i][k]+y[i][j]*y[i][k]+z[i][j]*z[i][k];
if(sum==0) cnt++;
}
}
if(cnt==6) ans2++;
}
if(ans1==8&&ans2==8) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
return ;
}
D. Shortest Path Quer
题意
给出一张图,其中任意一边一个端点的编号一定是另一个端点的编号的前缀,每一个边都有自己对应的权值。现在有 q q q次询问,每次询问请输出两点之间的最短路的权值,如果不存在输出 − 1 -1 −1。
题解
这题就像是汤神说的,相信它的复杂度然后直接冲就行了。
本题用到了 l c a lca lca和 t r i e trie trie树的思想。我们将所有的节点放到字典树上,那么所有节点只会和自己的孙节点和祖先节点之间有边而不会和自己祖先节点另外分枝上的子节点有边相连。所以我们求两点之间的最短路时,两点的最短路一定会经过两点的公共祖先。所以我们只要求出每个节点到自己子节点的最短路,然后输出就行。当然我们在求最短路时候要做一个小剪枝,不去求当前节点祖先节点的最短路。复杂度证明直觉上是能证的,但多少有点复杂。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
#ifdef ACM_LOCAL
const int N=1e1+10,M=2e1+10;
#else
const int N=1e5+10,M=2e5+10;
#endif
struct edge{int to,p,next;}e[M<<1];
int head[N],Len[N],cnt;
int n,m;
void add(int u,int v,int w){
e[++cnt].to=v;
e[cnt].p=w;
e[cnt].next=head[u];
head[u]=cnt;
return ;
}
int len(int x){int ans=0;while(x)x>>=1,ans++;return ans;}
unordered_map<int,unordered_map<int,ll>> dis;
unordered_map<int,unordered_map<int,bool>> vis;
void dij(int start){
priority_queue<pair<int,int>> q;
q.push({0,start});
dis[start][start]=0;
while(!q.empty()){
auto x=q.top();
q.pop();
int pos=x.second;
int w=-x.first;
if(vis[start][pos]==1) continue;
for(int i=head[pos];i;i=e[i].next){
int v=e[i].to;
if(Len[v]<=Len[start]) continue;
if(dis[start].count(v)==0||dis[start][v]>w+e[i].p){
dis[start][v]=w+e[i].p;
q.push({-dis[start][v],v});
}
}
}
}
int lca(int x,int y){
while(x!=y) if(x>y) x>>=1; else y>>=1;
return x;
}
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
void write(int x)
{
if(x<0)
putchar('-'),x=-x;
if(x>9)
write(x/10);
putchar(x%10+'0');
return;
}
signed main(){
#ifdef ACM_LOCAL
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
#endif
n=read();m=read();
for(int i=1,u,v,w;i<=m;i++){
u=read();v=read();w=read();
add(u,v,w),add(v,u,w);
}
for(int i=1;i<=n;i++) Len[i]=len(i);
for(int i=1;i<=n;i++) dij(i);
int q;
q=read();
while(q--){
int x,y;
x=read();y=read();
int LCA=lca(x,y);
ll ans=0x3f3f3f3f3f3f3f3f;
while(LCA){
if(dis[LCA].count(x)&&dis[LCA].count(y)) ans=min(ans,dis[LCA][x]+dis[LCA][y]);
LCA>>=1;
}
write(ans==0x3f3f3f3f3f3f3f3f?-1:ans);
putchar('\n');
}
return 0;
}
F. Fair Distribution
题意
有 n n n和 m m m两个值,你可以选择 n − − n-- n−−或者 m + + m++ m++这两种操作,请问最少经过多少次操作可以使两者成倍数关系。
题解
先进行两种特殊情况判断
-
n > m n>m n>m
这种情况下无论是 n n n减少还是 m m m增加答案都是 n − m n-m n−m。
-
m = = k n m==kn m==kn
原本就已经成比例了,直接输出 0 0 0就行了。
现在讨论 n < m n<m n<m
假设 n n n减少了 x x x。
我们可以推出公式:
n
−
(
n
−
x
)
+
(
(
m
−
1
)
/
(
n
−
x
)
+
1
)
∗
(
n
−
x
)
−
m
n-(n-x)+((m-1)/(n-x)+1)*(n-x)-m
n−(n−x)+((m−1)/(n−x)+1)∗(n−x)−m
化简可得
(
m
−
1
)
/
(
n
−
x
)
∗
(
n
−
x
)
+
n
−
m
(m-1)/(n-x)*(n-x)+n-m
(m−1)/(n−x)∗(n−x)+n−m
其中右边的 n − m n-m n−m是确定的,左边的 n − x n-x n−x是不确定的。如果我们枚举 n − x n-x n−x就能够得出最后的答案,不过考虑到 n n n的数据范围,这样的操作依然是不完备的。
随着 x x x变大 ( m − 1 ) / ( n − x ) (m-1)/(n-x) (m−1)/(n−x)也随着变大,同时 n − x n-x n−x变小我们能够但并不是每次 x + + x++ x++ ( m − 1 ) / ( n − x ) \ (m-1)/(n-x) (m−1)/(n−x)都会变化,我们只要选择每次恰好变化的 x x x来计算就行了。我们令 l = n − x l=n-x l=n−x同时 r r r为 ( m − 1 ) / l = = ( m − 1 ) / r (m-1)/l==(m-1)/r (m−1)/l==(m−1)/r的最大值,标记下一个左边界左边一位。
void MAIN(){
int n,m;
cin>>n>>m;
if(n>=m){cout<<n-m<<endl;return;}
if(m%n==0){cout<<0<<endl;return;}
int ans=inf;
for(int l=1,r;l<=n;l=r+1){
r=(m-1)/((m-1)/l);
ans=min(ans,(m-1)/l*l+(n-m));
}
cout<<ans<<endl;
return ;
}
G. Wall Game
题意
假设你有一张图上面有无数个蜂格,假设一个蜂格的左边为 ( x , y ) (x,y) (x,y)与他相邻的蜂格的坐标分别为 f x [ 10 ] = − 1 , − 1 , 0 , 1 , 1 , 0 , f y [ 10 ] = 1 , 0 , − 1 , − 1 , 0 , 1 fx[10]={-1,-1,0,1,1,0},fy[10]={1,0,-1,-1,0,1} fx[10]=−1,−1,0,1,1,0,fy[10]=1,0,−1,−1,0,1
你可以有两个操作,一是选择攻占某一个蜂格,二是询问某一个蜂格以及其联通块相对于外界有多少条边。
题解
带权并查集。每个点初始权值为 6 6 6代表你询问这条边的时候答案为 6 6 6。每次攻占一个点之后遍历所有与他相邻的点,如果也已经被攻占过了,那我们就可以将这两个点合并集合,同时将这两个点的权值相加减 2 2 2因为无论是哪两个蜂窝合并都可以减少两条边。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
int fx[10]={-1,-1,0,1,1,0};
int fy[10]={1,0,-1,-1,0,1};
unordered_map<int,pair<int,int>> fa;
unordered_map<int,int> siz;
unordered_map<int,bool> flag;
int n,out;
int _hash(pair<int,int> pir){
return pir.first*500010+pir.second;
}
pair<int,int> find(pair<int,int> x){
return fa[_hash(x)]=(fa[_hash(x)]==x?fa[_hash(x)]:find(fa[_hash(x)]));
}
void merge(pair<int,int> x,pair<int,int> y){
auto fax=find(x);
auto fay=find(y);
if(fax!=fay){
fa[_hash(fax)]=fay;
siz[_hash(find(fax))]=siz[_hash(fax)]+siz[_hash(fay)];
}
return ;
}
int read() {
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
void write(int x) {
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
void MAIN(){
n=read();
for(int i=1;i<=n;i++){
int op=read(),x=read(),y=read();
pair<int,int> node={x,y};
if(op==1){
fa[_hash(node)]=node;
siz[_hash(node)]=6;
flag[_hash(node)]=1;
out+=6;
for(int j=0;j<6;j++){
pair<int,int> adj={x+fx[j],y+fy[j]};
pair<int,int> emp={0,0};
if(fa[_hash(adj)]==emp)fa[_hash(adj)]=adj;
if(flag[_hash(adj)]==0) continue;
merge(node,adj);
siz[_hash(find(node))]-=2;out-=2;
}
}else{
if(flag[_hash(node)]==0)
write(out),putchar('\n');
else
write(siz[_hash(find(node))]),putchar('\n');
}
}
return ;
}
I. Grammy and Ropes
题意
三个环放在一起有六个交点。现在告诉你每个节点是编号较大的在上还是编号节点较小的在下面,请问现在要减去若干个圈使得三个圈分开,请问有多少种方式?
题解
先讨论一般情况。当两个环的交点上下不一时,我们可以认为这两个环套在了一起,计算有多少对环套在了一起
- 0 0 0:随便选择减或不剪或者剪几条,一共 8 8 8种情况
- 1 1 1:这一对环中随便选择一个剪了,其他的随意,一共 6 6 6种情况
- 2 2 2:将 3 3 3个环尝试拉开,要么将中间的环剪掉要么将两边的环剪掉,其他的随意,一共 5 5 5种情况
- 3 3 3:任选两个环剪掉,一共 4 4 4种情况
最后讨论一种特殊情况 1 1 1在 2 2 2上, 2 2 2在 3 3 3上, 3 3 3在 1 1 1上,这种情况下 3 3 3个环会套在一起必须剪掉一个才符合要求一共有 7 7 7种情况。
void MAIN(){
cin>>s[1][0]>>s[2][0]>>s[3][0]>>s[1][1]>>s[2][1]>>s[3][1];
int cnt=0;
for(int i=1;i<=3;i++) if(s[i][0]!=s[i][1]) cnt++;
if(cnt==0) {
for(int i=1;i<=3;i++){
for(int j=i+1;j<=3;j++){
int k;
if(i==1&&j==2) k=1;
if(i==1&&j==3) k=2;
if(i==2&&j==3) k=3;
if(s[k][0]==s[k][1]&&s[k][0]=="true") on[j][i]=1;
if(s[k][0]==s[k][1]&&s[k][0]=="false") on[i][j]=-1;
}
}
int flag=0;
for(int i=1;i<=3;i++){
for(int j=1;j<=3;j++){
if(i==j)continue;
for(int k=1;k<=3;k++){
if(i==k||j==k) continue;
if(on[i][j]==on[j][k]&&on[k][i]==on[i][j]){flag=1;break;}
}
if(flag==1) break;
}
if(flag==1) break;
}
if(flag) cout<<7<<endl;
else cout<<8<<endl;
}else{
if(cnt==1) cout<<6<<endl;
if(cnt==2) cout<<5<<endl;
if(cnt==3) cout<<4<<endl;
}
return ;
}
J. Grammy and Jwerly
题意
一张图上有 n n n个节点,每个节点上面有无限份价值为 v [ i ] v[i] v[i]的财宝。一个人可以从节点 1 1 1出发到每个节点上拿财宝,一次只能拿一份,放回 1 1 1节点之后算入总和。请问 t = 1 ∼ T t=1\sim T t=1∼T的时间里最多能拿价值总和的财宝?
题解
我们可以讲到某个点取财宝然后放回来的时间作为花费,将财宝的 v v v值作为对答案的贡献。于是我们就讲这个问题转化成了一个完全背包问题。
void MAIN(){
cin>>n>>m>>t;
g.resize(n+1);
for(int i=2;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
if(u==v) continue;
g[u].push_back(v);
g[v].push_back(u);
}
queue<pair<int,int>> q;
q.push({1,0});
while(!q.empty()){
auto x=q.front();
q.pop();
int pos=x.first;
int t=x.second;
for(auto v:g[pos]){
if(d[v]) continue;
d[v]=2*(t+1);
q.push({v,t+1});
}
}
for(int i=1;i<=n;i++){
for(int j=d[i];j<=t;j++){
dp[j]=max(dp[j],dp[j-d[i]]+a[i]);
}
}
for(int i=1;i<=t;i++) cout<<dp[i]<<" ";
cout<<endl;
return ;
}
L. String Freshman
题意
给出一份代码
int Find_Answer () {
int j = 1 , ans = 0;
for ( int i = 1; i <= n ; i ++) {
if (S[i] != T[j]) j = 1;
if (S[i] == T[j]) j ++;
if (j > m) {
ans ++;
j = 1;
}
}
return ans;
}
给出一个字符串 T T T请问当他在匹配的时候是否会存在一个字符串 S S S使得答案错误?
题解
这份代码所做的就是在每次失配的时候将指针回跳到 1 1 1的位置,这和 k m p kmp kmp算并不符合。但这不代表这个算法在所有情况下都是错误的,如果每个 n e x t next next数组都回跳到第一位这个算法还是正确的,我们可以直接通过判断 n e x t next next数组的方式来判断是否会答案错误。
const int N=1e5+10;
char s[N];
int nxt[N]={-1},len;
void pre(){
int j=0,k=-1;
while(j<len){
if(k==-1||s[j]==s[k]) nxt[++j]=++k;
else k=nxt[k];
}
}
void MAIN(){
cin>>len>>s;
pre();
int flag=0;
for(int i=1;i<=len;i++) if(nxt[i]!=0) flag=1;
if(flag==1) cout<<"Wrong Answer"<<endl;
else cout<<"Correct"<<endl;
return ;
}
L. Game Theory
题意
两个人,一个人给另一个人 x x x点,另一个人给这个人 y y y点,同时,如果 x > y x>y x>y前一个人获得 10 10 10点,如果 x < y x<y x<y后一个人获得 10 10 10点。请问前一个人最后获得点数的期望值。
题解
两人策略完全对称,胜负对等,期望为 0 0 0。
void MAIN(){
int n;
cin>>n;
cout<<0<<endl;
return ;
}