文章目录
比赛link
A、Strange Partition
题意
- 操作1:给定一个数组,可以将相邻的两个元素替换成这两个的和。比如 3 , 6 , 9 3,6,9 3,6,9可以变成 9 , 9 9,9 9,9,也能变成 3 , 15 3,15 3,15。这个操作可以做任意次。
- 概念1:一个数组 b b b的 b e a u t y beauty beauty值是 ∑ i = 1 n ⌈ b i x ⌉ \sum_{i=1}^{n}\lceil \frac{b_i}{x} \rceil ∑i=1n⌈xbi⌉。其中x是输入时给定的定值。
现在给定一个数组,可以做任意次数的操作1,求能得到的最小的 b e a u t y beauty beauty值以及最大的 b e a u t y beauty beauty值。
思路
很显然将所有元素加起来合并得到的 b e a u t y beauty beauty会是最小值,不做任何操作能得到的 b e a u t y beauty beauty值是最大值。
代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("-------Bug is here!-------")
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define enter putchar('\n')
#define mem(a,b) memset(a,b,sizeof(a))
#define umap unordered_map
#define uset unordered_set
#define pqueue priority_queue
using namespace std;
ll read(){
char _ch=' ';
ll _ans=0;
while(_ch<'0'||_ch>'9') _ch=getchar();
while(_ch<='9'&&_ch>='0'){
_ans=_ans*10+_ch-'0';
_ch=getchar();
}
return _ans;
}
void out(int _a){
if(_a>9){
out(_a/10);
}
putchar(_a%10 + '0');
}
ll t,n,x;
ll qwq[100005];
ll ans=0;
ll getans(ll a){
if(a%x==0){
return a/x;
}
a=a/x;
return a+1;
}
int main(){
t=read();
while(t--){
n=read();
x=read();
ans=0ll;
for(int i=1;i<=n;i++){
qwq[i]=read();
ans+=qwq[i];
}
printf("%lld ",getans(ans));
ans=0;
for(int i=1;i<=n;i++){
ans+=getans(qwq[i]);
}
printf("%lld\n",ans);
}
return 0;
}
B、Strange List
题意
- 操作1:遍历数组。如果当前元素不能整除以 x x x(输入时给定的定值),操作停止。否则,将x个当前元素除以x的值放到数组的末尾。比如给定 x x x是 2 2 2,数组是 12 12 12,变成 12 , 6 , 6 12,6,6 12,6,6,然后是 12 , 6 , 6 , 3 , 3 12,6,6,3,3 12,6,6,3,3,再然后是 12 , 6 , 6 , 3 , 3 , 3 , 3 12,6,6,3,3,3,3 12,6,6,3,3,3,3。然后停止。
给定数组和x,求操作停止之后这个数组的和是多少。
思路
我们并不能将数组所有元素都除尽 x x x然后算总和,因为可能出现中间有元素不能整除以 x x x导致停止。我们发现,其实每能除尽一次,对总和的贡献多了一次本体。也就是说每次加一次原来的值便可以。这样的话,我们copy一个原数组,然后一次次地遍历,每次除以 x x x。一旦不能整除以,操作停止。如果可以,那么就在答案上加上一次原来的值。这样做的时间复杂度是 O ( n l o g A ) O(nlogA) O(nlogA),合格。
代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("-------Bug is here!-------")
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define enter putchar('\n')
#define mem(a,b) memset(a,b,sizeof(a))
#define umap unordered_map
#define uset unordered_set
#define pqueue priority_queue
using namespace std;
ll read(){
char _ch=' ';
ll _ans=0;
while(_ch<'0'||_ch>'9') _ch=getchar();
while(_ch<='9'&&_ch>='0'){
_ans=_ans*10+_ch-'0';
_ch=getchar();
}
return _ans;
}
void out(int _a){
if(_a>9){
out(_a/10);
}
putchar(_a%10 + '0');
}
ll t,n,x,qwq[200005],minn,ans;
ll tmp[200005];
void solve(){
for(int i=1;i<=n;i++){
tmp[i]=qwq[i];
}
bool flag=false;
while(1){
if(flag) break;
for(int i=1;i<=n;i++){
if(tmp[i]%x==0){
ans+=qwq[i];
}
else{
flag=true;
break;
}
tmp[i]/=x;
}
}
}
int main(){
t=read();
while(t--){
n=read();
x=read();
minn=1e9;
ans=0ll;
for(int i=1;i<=n;i++){
qwq[i]=read();
ans+=qwq[i];
if(qwq[i]==1e9){
if(qwq[i]%x!=0ll){
minn=qwq[i];
}
}
}
if(minn==1e9){
solve();
}
else{
for(int i=1;i<=n;i++){
if(qwq[i]%x!=0) break;
ans+=qwq[i];
}
}
printf("%lld\n",ans);
}
return 0;
}
C、Strange Birthday Party
题意
给朋友送礼。给定了两个数组。
- 数组 k k k:对于每个朋友给定的一个值。对于 k i k_i ki,要么在前 k i k_i ki个礼物中选一个礼物送给第 i i i个朋友,要么给他买第 i i i个礼物所需要的钱
- 数组 c c c:每个礼物的价格。非降排列。
需要注意的是,朋友有 n n n个,但是礼物有 m m m个,并不一定相同。每个礼物只能买一次。求最小花费。
思路
看似很复杂,但是题意中表明,礼物的价格是非降排列。这样来说,我们可以直接找到最大花费的方案,也就是所有的朋友都不送礼物,都直接给钱。我们先把最大花费算出来,然后使用双指针逐渐降低花费。
双指针操作:先设置一个桶 v i s vis vis,把数组 k k k映射到数组 c c c中。也就是开桶统计 k i k_i ki的值。最大花费就是 ∑ m i = 1 c [ i ] × v i s [ i ] \sum_{m}^{i=1}c[i] \times vis[i] ∑mi=1c[i]×vis[i]。然后设置一个变量 i i i指向开头,从头到尾遍历;再设置一个变量 j j j指向结尾,从尾到头遍历。也就是用最小的花费的礼物去替换最大的钱数。由于礼物只能买一次,买过之后 i i i向后移动, v i s [ j ] vis[j] vis[j]减一。直到两个变量相遇,循环停止。因为此时给钱和给礼物的花费是相同的。这时候我们就从最大花费得到了最小花费。
代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("-------Bug is here!-------")
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define enter putchar('\n')
#define mem(a,b) memset(a,b,sizeof(a))
#define umap unordered_map
#define uset unordered_set
#define pqueue priority_queue
using namespace std;
int read(){
char _ch=' ';
int _ans=0;
while(_ch<'0'||_ch>'9') _ch=getchar();
while(_ch<='9'&&_ch>='0'){
_ans=_ans*10+_ch-'0';
_ch=getchar();
}
return _ans;
}
void out(int _a){
if(_a>9){
out(_a/10);
}
putchar(_a%10 + '0');
}
const int N=3e5+5;
int t,n,m,k[N],c[N],vis[N];
ll ans;
int main(){
t=read();
while(t--){
n=read();
m=read();
ans=0ll;
for(int i=1;i<=n;i++){
k[i]=read();
}
for(int i=1;i<=m;i++){
c[i]=read();
vis[i]=0;
}
for(int i=1;i<=n;i++){
vis[k[i]]++;
ans+=c[k[i]];
}
for(int i=1,j=m;j>i;j--){
if(vis[j]==0) continue;
while(j>i&&vis[j]){
ans=ans-c[j]+c[i];
i++;
vis[j]--;
}
}
for(int i=1;i<=m;i++){
vis[i]=0;
}
printf("%lld\n",ans);
}
return 0;
}
D、Strange Definition
题意
- 概念1:如果有两个正整数 x x x和 y y y,满足 l c m ( x , y ) g c d ( x , y ) \frac{lcm(x,y)}{gcd(x,y)} gcd(x,y)lcm(x,y)是平方数,也就是一个整数的平方,那么称 x x x和 y y y是相邻的。
- 概念2:在一个数组中,设 d i d_i di代表与数组元素 a i a_i ai相邻的数的数量(包括自身,没有相邻的就是1)。
- 概念3:一个数组的 b e a u t y beauty beauty值是指这个数组中最大的 d i d_i di的值。
- 概念4:在一个数组中,每过一秒,相邻数都会被替换成他们的乘积。比如样例 12 , 3 , 20 , 5 , 80 , 1 12,3,20,5,80,1 12,3,20,5,80,1,其中12和3相邻,20 5 80 相邻。1和自己相邻。第一秒就变成 36 , 36 , 8000 , 8000 , 8000 , 1 36,36,8000,8000,8000,1 36,36,8000,8000,8000,1。
现在给出一个数组,然后 q q q次询问,每次询问第 w w w秒这个数组的 b e a u t y beauty beauty值。
思路
先来看相邻这个概念:
l
c
m
(
x
,
y
)
g
c
d
(
x
,
y
)
=
x
⋅
y
g
c
d
2
(
x
,
y
)
\begin{aligned} \frac{lcm(x,y)}{gcd(x,y)} & =\frac{x·y}{gcd^2(x,y)} \end{aligned}
gcd(x,y)lcm(x,y)=gcd2(x,y)x⋅y
我们发现,如果这个数是一个平方数,那么
x
⋅
y
x·y
x⋅y也得是一个平方数。这个结论是显然的,要不然开方的话,分母能开出整数分子不能。
这样的话,我们判断两个数是否相邻,直接判断两个数的乘积是否是平方数即可。但是这样的话,本质上算法时间复杂度并没有减少很多,判断相邻这边依旧是 O ( n 2 ) O(n^2) O(n2)。那么就从判断乘积这里下手。
我们将两个数分解成质因数分解的形式,即 x = p 1 k 1 p 2 k 2 . . . p n k n x=p_1^{k_1}p_2^{k_2}...p_n^{k^n} x=p1k1p2k2...pnkn, y = P 1 K 1 P 2 K 2 . . . P m K m y=P_1^{K_1}P_2^{K_2}...P_m^{K_m} y=P1K1P2K2...PmKm。这两个数的乘积如果是平方数的话,这两个数的公有的奇数次的质因子相乘后变为偶数次,公有的偶数次质因子相乘后仍然是偶数次。这两个部分对 x ⋅ y x·y x⋅y是否是平方数并没有影响。两个数不公有的部分决定了 x ⋅ y x·y x⋅y是否是平方数。两个数不公有的偶数次质因数显然也没有影响,但是如果存在有不公有的奇数次质因子,就会让 x ⋅ y x·y x⋅y直接变成非平方数。
那么,根据上面的分析,我们可以开一个桶,对于每个数,都一直去除以一些平方数,直到只剩下奇数次的质因数的乘积,然后用桶统计。如果两个数剩下的奇数次的质因子的乘积相同,这两个数的乘积必然是平方数,那么也就是说这两个数是相邻的。依然是样例 12 , 3 , 20 , 5 , 80 , 1 12,3,20,5,80,1 12,3,20,5,80,1,这样处理之后就变成 3 , 3 , 5 , 5 , 5 , 1 3,3,5,5,5,1 3,3,5,5,5,1。显然可以看出前两个数是相邻的,中间三个 5 5 5是相邻的。这样就让寻找相邻这个部分变成了 O ( n l o g A ) O(nlogA) O(nlogA)级别。
然后就是寻找每一秒数组的 b e a u t y beauty beauty值的问题了。还是看样例 12 , 3 , 20 , 5 , 80 , 1 12,3,20,5,80,1 12,3,20,5,80,1,处理后变成 3 , 3 , 5 , 5 , 5 , 1 3,3,5,5,5,1 3,3,5,5,5,1,可以得出这个数组在第0秒的时候 b e a u t y beauty beauty值是3。第一秒变成 36 , 36 , 8000 , 8000 , 8000 , 1 36,36,8000,8000,8000,1 36,36,8000,8000,8000,1,继续处理变成 1 , 1 , 5 , 5 , 5 , 1 1,1,5,5,5,1 1,1,5,5,5,1。我们发现,相邻数量是偶数的变成了1,和1成了相邻数。并且可以证明,两个偶数的块在这一秒也是互相相邻。但是奇数次的就保持不变。继续推发现,答案只存在与第0秒和第1秒。第1到正无穷秒的答案都是一样的。根据上面的规律,第0秒的答案就是遍历整个桶,维护最大值。第一秒的答案,先把偶数次以及本身就是1的加起来,然后和第0秒的答案取一次max。本题完毕。
代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("-------Bug is here!-------")
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define enter putchar('\n')
#define mem(a,b) memset(a,b,sizeof(a))
#define umap unordered_map
#define uset unordered_set
#define pqueue priority_queue
using namespace std;
int read(){
char _ch=' ';
int _ans=0;
while(_ch<'0'||_ch>'9') _ch=getchar();
while(_ch<='9'&&_ch>='0'){
_ans=_ans*10+_ch-'0';
_ch=getchar();
}
return _ans;
}
ll Read(){
char _ch=' ';
ll _ans=0;
while(_ch<'0'||_ch>'9') _ch=getchar();
while(_ch<='9'&&_ch>='0'){
_ans=_ans*10+_ch-'0';
_ch=getchar();
}
return _ans;
}
void out(int _a){
if(_a>9){
out(_a/10);
}
putchar(_a%10 + '0');
}
int t,n,q,a[300005],d,tmp;
ll w;
umap<int,int> mmp;
int main(){
t=read();
while(t--){
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
for(int j=2;j*j<=a[i];j++){
tmp=j*j;
while(a[i]%tmp==0){
a[i]/=tmp;
}
}
mmp[a[i]]++;
}
int ini=0,after=0;
for(auto i:mmp){
ini=max(ini,i.second);
if(i.first==1||(i.second%2==0)) after+=i.second;
}
after=max(after,ini);
q=read();
while(q--){
w=Read();
printf("%d\n",w==0?ini:after);
}
mmp.clear();
}
return 0;
}
E、Strange Shuffle
题意
这是一道交互题。输入给定两个数 n n n和 k k k, n n n是数组长度, k k k是数组中每个数的值。也就是说这个数组在一开始的时候每个位置的值是相同的。 k k k保证是偶数。这个数组是首尾相接的,也就是说位置 1 1 1的左邻居是 n n n,位置 n n n的右邻居是 1 1 1。换言之, 1 − 1 = n 1-1=n 1−1=n, n + 1 = 1 n+1=1 n+1=1。在每一回合,每个数都把自己 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor ⌊2x⌋的值给左边, ⌈ x 2 ⌉ \lceil \frac{x}{2} \rceil ⌈2x⌉的值给右边。我们可以发现如果每个数都这样,这个数组的值都是恒定不变的。但是这些数中有一个内鬼,在每一回合都将自己的全部值给右边。
交互部分:输出"? q",表示查询 q q q位置的值。在查询之前,这个数组会进行一回合,然后返回 q q q位置的值。询问次数不能超过 1000 1000 1000次。当找到内鬼所在位置之后,输出"! q"表示 q q q位置是内鬼。
思路
我们可以先模拟打表找规律,代码中p的值是内鬼的位置:
打表代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("-------Bug is here!-------")
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define enter putchar('\n')
#define mem(a,b) memset(a,b,sizeof(a))
#define umap unordered_map
#define uset unordered_set
#define pqueue priority_queue
using namespace std;
int read(){
char _ch=' ';
int _ans=0;
while(_ch<'0'||_ch>'9') _ch=getchar();
while(_ch<='9'&&_ch>='0'){
_ans=_ans*10+_ch-'0';
_ch=getchar();
}
return _ans;
}
void out(int _a){
if(_a>9){
out(_a/10);
}
putchar(_a%10 + '0');
}
int qwq[50],tmp1[50],tmp2[50],n,k,p;
int rightN(int x){
if(x==n) return 1;
return x+1;
}
int leftN(int x){
if(x==1) return n;
return x-1;
}
int main(){
p=5;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) qwq[i]=k;
for(int i=1;i<=n;i++) printf("%d ",qwq[i]);
enter;
while(1){
int a=read();
if(a==0) break;
for(int i=1;i<=n;i++){
tmp1[i]=qwq[i]/2;
tmp2[i]=qwq[i]-tmp1[i];
}
for(int i=1;i<=n;i++){
if(i==p){
qwq[i]-=tmp1[i]+tmp2[i];
qwq[rightN(i)]+=tmp1[i]+tmp2[i];
}
else{
qwq[leftN(i)]+=tmp1[i];
qwq[rightN(i)]+=tmp2[i];
qwq[i]-=tmp1[i]+tmp2[i];
}
}
for(int i=1;i<=n;i++) printf("%d ",qwq[i]);
enter;
}
return 0;
}
打出表之后我们可以发现,内鬼位置的值一直不变,恒定为 k k k。进行回合之后,内鬼的左边必定小于 k k k,内鬼的右边必定大于 k k k。而且一开始每进行一回合,都会增加一个大于 k k k的数在右边。
看规律可以看出,以内鬼位置的下一个位置为起点,其实相当于一个递降(非升)的序列。我们可以先找到一个大于 k k k的位置,这个位置必定在内鬼的右边。然后以这个位置的下个位置为起点,向右拓展(这个数组是循环数组首尾相接),设置左边界和右边界,然后二分查找。
关于找第一个大于 k k k的数,可以采用分块的思想,先进行 n \sqrt n n个回合,然后按照块的大小进行查找,降低复杂度。
AC代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("-------Bug is here!-------")
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define enter putchar('\n')
#define mem(a,b) memset(a,b,sizeof(a))
#define umap unordered_map
#define uset unordered_set
#define pqueue priority_queue
using namespace std;
int read(){
char _ch=' ';
int _ans=0;
while(_ch<'0'||_ch>'9') _ch=getchar();
while(_ch<='9'&&_ch>='0'){
_ans=_ans*10+_ch-'0';
_ch=getchar();
}
return _ans;
}
void out(int _a){
if(_a>9){
out(_a/10);
}
putchar(_a%10 + '0');
}
int n,k;
int ask(int x){
cout<<"? "<<x<<endl;
cout.flush();
int tmp;
cin>>tmp;
return tmp;
}
int right(int x){
if(x==n) return 1;
return x+1;
}
int left(int x){
if(x==1) return n;
return x-1;
}
int main(){
cin>>n>>k;
int lim=sqrt(n);
for(int i=1;i<=lim;i++){
ask(i);
}
int l=1,r=n,mid,tmp;
int add=lim-1;
for(int i=1;i<=n;i+=add){
if(ask(i)>k){
l=i+1;
r=i+n;
break;
}
}
while(l<r){
mid=l+r>>1;
tmp=mid>n?mid-n:mid;
if(ask(tmp)>k) r=mid;
else l=mid+1;
}
l--;
if(l<1) l+=n;
if(l>n) l-=n;
cout<<"! "<<l<<endl;
cout.flush();
return 0;
}
F、Strange Housing
题意
- 条件1:整张图都应该是联通的
- 条件2:如果两个点都没人,这两个点之间的边就移除
- 条件3:一条边的两端不能都住人
给出一张图,求尽可能住人的方案。如果没有方案,就输出NO。
思路
显然,一条边的两端不能都住人,还要保证整张图是联通的。那么这就是一个给图染色的问题。我们把住人的点染色为1,不住人的点染色为-1。只要是能住人(周围没有1),那就染成1。一遍dfs即可。重点:保证整张图是连通图即可,一条边两端都是-1也可以。
代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("-------Bug is here!-------")
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define enter putchar('\n')
#define mem(a,b) memset(a,b,sizeof(a))
#define umap unordered_map
#define uset unordered_set
#define pqueue priority_queue
using namespace std;
int read(){
char _ch=' ';
int _ans=0;
while(_ch<'0'||_ch>'9') _ch=getchar();
while(_ch<='9'&&_ch>='0'){
_ans=_ans*10+_ch-'0';
_ch=getchar();
}
return _ans;
}
void out(int _a){
if(_a>9){
out(_a/10);
}
putchar(_a%10 + '0');
}
const int N=3e5+5;
struct node
{
int to,nxt;
}edge[N<<1];
int t,n,m,cnt,head[N],color[N];
void add(int u,int v){
edge[cnt].to=v;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
void dfs(int u,int fa){
int flag=0;
for(int i=head[u];i>0;i=edge[i].nxt){
int son=edge[i].to;
if(color[son]==1){
flag=1;
break;
}
}
if(flag) color[u]=-1;
else color[u]=1;
for(int i=head[u];i>0;i=edge[i].nxt){
int son=edge[i].to;
if(color[son]==0) dfs(son,u);
}
}
int main(){
t=read();
while(t--){
cnt=1;
n=read(),m=read();
for(int i=1;i<=m;i++){
int a=read(),b=read();
add(a,b);
add(b,a);
}
dfs(1,1);
int con=1,sum=0;
for(int i=1;i<=n;i++){
if(color[i]==0){
con=0;
break;
}
if(color[i]==1) sum++;
}
if(con){
printf("YES\n%d\n",sum);
for(int i=1;i<=n;i++){
if(color[i]==1){
printf("%d ",i);
}
}
enter;
}
else{
printf("NO\n");
}
for(int i=1;i<=n;i++){
color[i]=0;
}
for(int i=1;i<=n;i++){
head[i]=0;
}
for(int i=1;i<=cnt;i++){
edge[i].to=0;
edge[i].nxt=0;
}
}
return 0;
}