Codeforces Round #738
賽時:4/6
A
注意到有這么一句話:any number of times.
我們又知道 & 運算總是不增的,所以就把所有數做 & 運算,答案一定是最優的。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000,INF=1e9;
int t,n,a[N],b[N],ans;
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
int main(){
t=read();
while(t--){
n=read();ans=INF;
for(int i=1;i<=n;++i){
a[i]=read();
if(i==1)ans=a[1];
else ans&=a[i];
}
printf("%d\n",ans);
}
return 0;
}
B
只要找到一個不是問號的字符就開始交替着填,直到又找到一個,重復以上操作即可。
再注意下有可能有很多前導問號,就從第一個不是問號的位置向前交替着填。
再再注意下如果全部都是問號直接特判就行
問什么這樣填就行??
比如這樣:R???B 或者 R????R ,如果按上面哪種方式填要重復一個,但倒過來想,這樣怎么填都是會至少重復一個,所以這樣直接搞就行。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int t,n;
char a[N];
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
int main(){
t=read();
while(t--){
n=read();
scanf("%s",a+1);
for(int i=1;i<=n;++i){
if(a[i]=='R'){
int sig=1;
for(int j=i+1;j<=n;++j){
if(a[j]!='?')break;
if(sig==1)a[j]='B';
else a[j]='R';
sig^=1;
}
}
if(a[i]=='B'){
int sig=1,id=i;
for(int j=i+1;j<=n;++j){
if(a[j]!='?')break;
if(sig==1)a[j]='R';
else a[j]='B';
sig^=1;id=j;
}
i=id;
}
}
for(int i=1;i<=n;++i){
if(a[i]=='R'){
int sig=1;
for(int j=i-1;j>=1;--j){
if(sig==1)a[j]='B';
else a[j]='R';
sig^=1;
}
break;
}
if(a[i]=='B'){
int sig=1;
for(int j=i-1;j>=1;--j){
if(sig==1)a[j]='R';
else a[j]='B';
sig^=1;
}
break;
}
}
int sigg=1;
for(int i=1;i<=n;++i){
if(a[i]=='?'){
if(sigg==1)a[i]='B';
else a[i]='R';
sigg^=1;
}
}
cout<<a+1<<endl;
}
return 0;
}
C
如果沒有第 n+1 個點的話,題目已經告訴我們路徑了,就是從 1 到 n 走就行,所以我們只要想怎么把 n+1 加進去就行了。
1.放到 1 前面;
2.放到 n 后面;
3.中間挑一個位置插進去,爽啊。
這已經包含了全部情況了,因為,看題目輸入:
1.條件是 \(\small a_1=1\) ;
2.條件是 \(\small a_n=0\) ;
3.條件是存在 \(\small i\in [2,n]\) 使得 \(\small a_{i-1}<a_i\) 。
然后我們發現前兩種情況撇掉后,已經構造不出數組 a 不滿足第三種情況,所以這題就很愉快的抬走力!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e4+10;
int t,n,a[N];
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
int main(){
t=read();
while(t--){
n=read();
for(int i=1;i<=n;++i){
a[i]=read();
}
if(a[n]==0){
for(int i=1;i<=n+1;++i)printf("%d ",i);
printf("\n");
continue;
}
if(a[1]==1){
printf("%d ",n+1);
for(int i=1;i<=n;++i)printf("%d ",i);
printf("\n");
continue;
}
int id=0;
for(int i=2;i<=n;++i){
if(a[i]>a[i-1]){
id=i;break;
}
}
if(id!=0){
for(int i=1;i<id;++i)printf("%d ",i);
printf("%d ",n+1);
for(int i=id;i<=n;++i)printf("%d ",i);
printf("\n");
continue;
}
}
return 0;
}
D
easy version
兩個圖造好后 \(\small n^2\) 暴力枚舉,只要兩個點在兩個圖里面都不在一個大塊里面就連邊。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10;
int n,m1,m2,fa1[N],fa2[N],siz1[N],siz2[N],ans;
struct mdzz{
int x,y;
}l[N];
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
inline int find1(int x){
if(fa1[x]==x)return fa1[x];
return fa1[x]=find1(fa1[x]);
}
inline int find2(int x){
if(fa2[x]==x)return fa2[x];
return fa2[x]=find2(fa2[x]);
}
int main(){
n=read();m1=read();m2=read();
for(int i=1;i<=n;++i){
fa1[i]=fa2[i]=i;
siz1[i]=siz2[i]=1;
}
for(int i=1;i<=m1;++i){
int u=read(),v=read();
int x=find1(u),y=find1(v);
fa1[x]=y;
}
for(int i=1;i<=m2;++i){
int u=read(),v=read();
int x=find2(u),y=find2(v);
fa2[x]=y;
}
if(n-1==m1||n-1==m2){
printf("0");
return 0;
}
for(int i=1;i<=n;++i){
for(int j=i+1;j<=n;++j){
int u1=find1(i),v1=find1(j);
int u2=find2(i),v2=find2(j);
if(u1!=v1&&u2!=v2){
fa1[u1]=v1;fa2[u2]=v2;
l[++ans]=(mdzz){i,j};
}
}
}
printf("%d\n",ans);
for(int i=1;i<=ans;++i){
printf("%d %d\n",l[i].x,l[i].y);
}
return 0;
}
hard version
發現以任何順序加入合法邊都不會影響答案
所以可以定一個點(就定 1 號點就行),先盡可能的把所有在兩個圖里面都不與 1 號點聯通的點連上
所以任意一點就只會剩下都與 1 號點聯通或其中一個圖與 1 號點聯通的兩種可能。
前者顯然不用管,后者的話又分兩類:
1.與圖一中的 1 號點聯通;
2.與圖二中的 1 號點聯通;
顯然,所有 1 類點都聯通,所有 2 類點都聯通
然后只要 1 類點與 2 類點連邊就行,直到不存在其中一類點就行完成力!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,m1,m2,fa1[N],fa2[N];
vector<int> ans,a,b;
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
inline int find1(int x){
if(fa1[x]==x)return fa1[x];
return fa1[x]=find1(fa1[x]);
}
inline int find2(int x){
if(fa2[x]==x)return fa2[x];
return fa2[x]=find2(fa2[x]);
}
int main(){
n=read();m1=read();m2=read();
for(int i=1;i<=n;++i){
fa1[i]=fa2[i]=i;
}
for(int i=1;i<=m1;++i){
int u=read(),v=read();
int x=find1(u),y=find1(v);
if(x!=y)fa1[x]=y;
}
for(int i=1;i<=m2;++i){
int u=read(),v=read();
int x=find2(u),y=find2(v);
if(x!=y)fa2[x]=y;
}
for(int i=1;i<=n;++i){
int x=find1(1),y=find1(i);
int u=find2(1),v=find2(i);
if(x==y||u==v)continue;
fa1[x]=y;fa2[u]=v;ans.push_back(i);
}
for(int i=1;i<=n;++i){
int x=find1(1),y=find1(i);
int u=find2(1),v=find2(i);
if(x!=y){fa1[x]=y;a.push_back(i);}
if(u!=v){fa2[u]=v;b.push_back(i);}
}
printf("%d\n",ans.size()+min(a.size(),b.size()));
for(int i=0;i<ans.size();++i)printf("1 %d\n",ans[i]);
for(int i=0;i<min(a.size(),b.size());++i)printf("%d %d\n",a[i],b[i]);
return 0;
}
E
假如沒有 gcd 的限制的話,應該能發現這是一個比較經典的 DP ,可以 \(\small O(nm)\) 做。
然后考慮把限制條件加進來,設上面 DP 的模型 \(\small f(a_1,a_2,...,a_n)\) 表示此數列能否滿足。
所以有很顯然的求和式子:
長得比較莫反的樣子,所以:
后面這一坨就又回到了去掉限制時分析的 DP 模型了。
於是我們在 \(\small O(\) \(\large {\frac{nm}{1}}\) \(\small +\) \(\large\frac{nm}{2}\) \(\small +\ ...\ +\) \(\large\frac{nm}{m}\) \(\small )\ \approx O(nm\ln m)\) (調和級數)的時間把這道題搞定力!
(但其實 \(\small \mu(d)==0\) 根本不用算后面,不過絲毫不慌,怎么着也過得了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=108,M=1e5+10;
const ll mod=998244353;
ll n,m,pri[M],cnt,mu[M];
ll f[N][M],sum[M],tmp,ans;
//f_i,j 表示前i個質數和(除了之后)為j的方案數
bool vis[M];
struct mdzz{
ll l,r,lt,rt;
}p[N];
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
inline void pre_mul(){
mu[1]=1;
for(int i=2;i<=m;++i){
if(!vis[i]){
pri[++cnt]=i;
mu[i]=-1;
}
for(int j=1;j<=cnt&&i*pri[j]<=m;++j){
vis[i*pri[j]]=1;
if(i%pri[j]==0){
mu[i*pri[j]]=0;
break;
}
mu[i*pri[j]]=-mu[i];
}
}
}
int main(){
n=read();m=read();
pre_mul();
for(int i=1;i<=n;++i){
p[i].l=read();p[i].r=read();
}
for(int d=1;d<=m/n;++d){
for(int i=1;i<=n;++i){
p[i].lt=p[i].l/d+(p[i].l%d!=0);
p[i].rt=p[i].r/d;
}
for(int i=1;i<=m/d;++i)f[1][i]=0;
for(int i=p[1].lt;i<=p[1].rt;++i)f[1][i]=1;
for(int i=2;i<=n;++i){
for(int j=1;j<=m/d;++j)sum[j]=(sum[j-1]+f[i-1][j])%mod;
for(int j=p[i].lt;j<=m/d;++j){
f[i][j]=(sum[j-p[i].lt]+mod-sum[j-min(1ll*j,p[i].rt+1)])%mod;
}
}
tmp=0;
for(int i=1;i<=m/d;++i)tmp=(tmp+f[n][i])%mod;
ans=(ans+tmp*mu[d]+mod)%mod;
}
printf("%lld\n",ans);
return 0;
}
Codeforces Round #739
賽時:3/7(wtcl)
A
暴力(+ 預處理)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10;
int t,k,a[N],cnt;
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
int main(){
for(int i=1;i<=3000;++i){
if(i%3&&i%10!=3)a[++cnt]=i;
if(cnt==1000)break;
}
t=read();
while(t--){
k=read();
printf("%d\n",a[k]);
}
return 0;
}
B
找到周期,然后對面的數字是(自己 + 周期的一半)%周期。
然后注意一下,周期一半的對面不是 0 ,特判一下。
最后算一算給的數據存不存在就行了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,a,b,c,num;
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
int main(){
t=read();
while(t--){
a=read();b=read();c=read();
num=(abs(b-a))<<1ll;
if(num<b||num<a||num<c){
printf("-1\n");
continue;
}
else {
if(c==(num>>1))printf("%d\n",num);
else printf("%d\n",(c+(num>>1))%num);
}
}
return 0;
}
C
這樣繞下去的話,每一次繞完,數字就到了圈數的平方。
所以可以先算繞的是哪一圈,然后判斷是在圈的列上還是在行上就行了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,k,id;
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
int main(){
t=read();
while(t--){
k=read();
for(ll i=0;;++i){
if(i*i<k&&k<=(i+1ll)*(i+1ll)){
id=i;break;
}
}
if(id<k-id*id){
printf("%d ",id+1);
printf("%d\n",(id+1)*(id+1)-k+1);
}
else {
printf("%d ",k-id*id);
printf("%d\n",id+1);
}
}
return 0;
}
D
因為數據最大是 9 位( \(\small 10^9\) 另算),假設現在最優解是去數去到 1 位,那么如果只需要補數的話至多會補到 9+(9-1)=17 位
所以產生答案的 2 的 k 次冪中, k 最大也只會達到 59 ( 60~62 都可以喲,ull可以再加上 63 但都沒什么用)。
k 不大,最長 19 位,完全可以暴力枚舉,又因為題目的要求,只會用到 \(\small 2^k\) 的前綴,所以只要算出原串和 \(\small 2^k\) 的最長公共子序列,
然后按照題目要求,答案就是原串長度 - 最長公共子序列長度 +\(\small 2^k\) 的長度 - 最長公共子序列長度。
因為原串長度 - 最長公共子序列長度就是指原串里面要去掉的部分。
而 \(\small 2^k\) 的長度 - 最長公共子序列長度就是指原串中缺少的部分。
加起來就是答案啦。
最后只要對所有 2 的 k 次冪算一遍答案,取個 min 就完成力!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF=1e9;
int t,ans;
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
inline void mian(){
std::string s;
std::cin >> s;
for(int i=0;i<=60;i++){
std::string t=std::to_string(1ll<<i);
int k=0;
for(int j=0;j<s.size();j++){
if(k<t.size()&&s[j]==t[k])++k;
}
ans=std::min(ans,int(s.size())+int(t.size())-k-k);
}
}
int main(){
t=read();
while(t--){
ans=INF;
mian();
printf("%d\n",ans);
}
return 0;
}
E
好像乍一看,直接硬上是不行的,看看哪里有突破口。
看了半天,發現好像輸出里面后面那個小串能直接算,只要從后往前掃。
最先出現的就是最后刪除的,
其次出現的就是倒數第二個刪除的,然后以此類推。
(不能順着找,誰知道原串是怎么排列的呀)
因為每次刪完這個字符,之后接到后面的串就不存在這個字符了,所以可以這樣直接求出。
(所以不能順着找呀)
感覺可以先求有解的情況下原串應該的長度,並且能夠算出原串每個字符要有多少個。
因為只要不刪掉這個字符,每次復制一遍的話,數量就會加倍,正好已經求出了小串:
對於第一個刪掉的字符,數量只被復制了一遍,
對於第二個刪掉的字符,數量被復制了兩遍,之后又是以此類推。
又因為,被刪掉后不會再出現,所以只要預處理 26 個字符各出現了幾次,就能算了(連哪個位置都不需要)
最后模擬一遍構造方式,驗證一下就行。
因為題目給的字符串有可能是亂寫的,導致計算原串長度的時候因為除不盡或者位置不對,反而算完長度之后不會檢查出來,所以再檢查一遍。
(不驗證的話其實可以去掉大部分無解的串,但不能做到滴水不漏,比如樣例 abacabaaacaac ,如果改成 abcaabaaacaac,即使預處理算 26 種字符的個數時加上了位置,也很難檢查出來,所以還是得檢查一遍至少比較方便。。)
於是我們在甚至不知道復雜度的情況下把這題過力!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n,m,cnt[28];
string s;
inline void clear(){
memset(cnt,0,sizeof(cnt));
}
inline void mian(){
clear();
string ans,tot;
cin>>s;n=s.size();
for(int i=n-1;~i;--i){
if(!cnt[s[i]-'a'+1])tot+=s[i];
++cnt[s[i]-'a'+1];
}
m=tot.size();n=0;
reverse(tot.begin(),tot.end());
for(int i=0;i<m;++i){
n+=(cnt[tot[i]-'a'+1])/(i+1);
//這一位應該在原串中出現幾次
}
if(n>s.size()){
printf("-1\n");
return ;
}
ans=s.substr(0,n);
string chk,add=ans;
for(int i=0;i<m;++i){
chk+=add;
string now;
for(int j=0;j<n;++j){
if(add[j]!=tot[i])now+=add[j];
}
add=now;n=add.size();
}
if(chk!=s){
printf("-1\n");
}
else cout<<ans<<" "<<tot<<"\n";
}
int main(){
scanf("%d",&t);
for(int i=1;i<=t;++i)mian();
return 0;
}
F
easy version
看到題目 \(k\leq 2\) ,着實小的可憐,可以用一堆 if 亂搞??(沒試過)
hard version
(注:下文“試填”表示用從當前這一位的數字 +1 到 9 去更換這一位,看是否合法)
可以想到應該盡可能不動高位,所以可以由高到低從第一個不合法的位置開始試填,然后分兩種情況:
1.發現這一位試填完了都不能使其合法,往高一位繼續試填;
2.發現這一位能夠試填到使其合法,往低一位繼續試填。
直到最低一位都合法的時候,這個數字就合法了。
於是我們又在不知道復雜度的情況下把這題過力!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n,k;
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*w;
}
inline int digcnt(int x){
int tmp=0,it;
while(x){
it=x%10;
tmp|=(1<<it);
x/=10;
}
return __builtin_popcount(tmp);
}
inline void mian(){
n=read();k=read();
while(digcnt(n)>k){
int l=1,r=n;
while(digcnt(r)>k){
l*=10;r/=10;
}
l/=10;
n=((n/l)+1)*l;
}
printf("%d\n",n);
}
int main(){
t=read();
for(int i=1;i<=t;++i)mian();
return 0;
}
Codeforces Round #742
賽時:2/6(wtcl)
A
因為 L 和 R 不影響上下,所以只要讓 D 和 U 互換就可以了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t, n;
char a[108];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
int main() {
t = read();
while (t--) {
n = read();
scanf("%s", a);
for (int i = 0; i < n; ++i) {
if (a[i] == 'D')cout<<'U';
else if (a[i] == 'U')cout<<'D';
else cout<<a[i];
}
cout<<endl;
}
return 0;
}
B
因為有一個 MEX ,所以長度至少 MEX 。
接下來分四種情況:
-
這 MEX 個數的 XOR 剛好滿足,長度就是 MEX 。
-
MEX 個數異或后還需要異或一個大於 MEX 的數,長度是 MEX + 1 。
-
MEX 個數異或后還需要異或一個等於 MEX 的數,長度就是 MEX + 2 ,因為 MEX 不能選進去,就需要通過兩個數“湊”出 MEX 。
-
MEX 個數異或后還需要異或一個小於 MEX 的數,長度是 MEX + 1 。
搞定力!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
int t, n, m, ans, tmp, a[N];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
int main() {
for(int i = 1; i <= 3e5; ++i)a[i] = a[i-1] ^ i;
t = read();
while (t--) {
n = read(); m = read(); ans = 0; tmp = 0;
ans = n;
tmp = a[n-1];
if (tmp == m) {
printf("%d\n", ans);
continue;
}
if ((tmp ^ m) == n) ans += 2;
else ++ans;
printf("%d\n", ans);
}
return 0;
}
C
這是一個關於進位的問題。
按照原來的法則,只向前進一位的話,整個操作會看起來很臃腫。比如: 44444 + 55556
但是,這道題非常好心的更改了法則,於是,
偶數位只會進位到偶數位,奇數位只會進位到偶數位,所以方案數就分成兩個部分:
-
偶數位的貢獻
-
奇數位的貢獻
所以把偶數位和奇數位分離出來成兩個數,根據很基本的加法和乘法原理,答案就是這分離出來的兩個數相乘。
等會,還沒完
哦,答案還要減一,因為是正整數。。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t, n, a, b, it, dig, sig;
inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void mian() {
n = read(); it = dig = 10;
sig = a = b = 0;
while (n) {
if (!sig) a = a + n % it * 10ll / dig;
else b = b + n % it / dig;
n -= n % it; it *= 10ll; sig ^= 1;
if (!sig) dig *= 10ll;
}
printf("%lld\n", a * b + a + b - 1);
}
int main() {
t = read();
while (t--) mian();
return 0;
}
D
無非就是兩個個進制之間的一個卡上限的數字游戲(大霧)
想想如果要最大,要怎么分
我們知道:
\(11_{(11)} = 1\cdot 11^1 + 1\cdot 11^0 = 12_{(10)}\)
好家伙那豈不是全部數拆成類似 &10^n& 不就在盡可能在變大嗎。
於是你這么干,發現樣例里面有一個不太對勁的東西。。
111 分成 4 份,這樣分肯定分不完呀。。
於是去學了學 CF 上碼量最小的提交,大概就是:
讓高位的 \(10^n\) 盡可能地保留,然后類似 10 分成 3 份的就拆成 3 3 4 ,讓上面那個反例變得合法並且保留盡可能多的貢獻。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int ksm[10], t, n, s, num;
inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void mian() {
s = read(); n = read();
for (int i = n - 1; i >= 1; -- i) {
num = ksm[(int)log10(s - i)];
printf("%d ", num); s -= num;
}
printf("%d\n", s);
}
int main() {
ksm[0] = 1;
for (int i = 1; i <= 9; ++ i) {
ksm[i] = ksm[i - 1] * 10;
}
t = read();
while (t--) mian();
return 0;
}
E
一眼數據結構題,但是好像並沒有什么能直接維護區間不下降子串數量的。
那先用直白明了的線段樹作為參考,想一想怎么做,
想來想去可能就只有合並會不一樣,其他的都是基本操作。(因為只有單點修改。。)
如何合並
不下降字串,對與要合並的兩個區間,分為三種情況:
-
只在左區間降的
-
只在右區間降的
-
橫跨在兩個區間降的
顯然,前面兩種答案不會互相影響,是可以直接相加的,
那么對於第三種的話:
兩個區間答案的合並
先來想答案是由什么構成的。
應該是等於(左區間從右端點向左能不上升的最遠距離)*(右區間從左端點向右能不下降的最遠距離)
同時注意到有可能左區間和最右邊的數可能大於右區間最左邊的數,這樣明顯就不存在橫跨兩個區間的字串了。
所以每個區間只要維護這上面提到的四個信息就可以了就可以了。
信息傳遞的話后兩者很明顯,對其與前兩者的話,肯定至少為左區間向右和右區間向左的距離。
那么如果還可以繼續擴大的話,就在記錄一下整個區間是否就是一個不下降串就能判斷了。
至此,就基本完成了這道題了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n, q, a[N];
struct mdzz {
ll val, l, r, L, R;
bool sig;
};
inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
struct SegmentTree {
mdzz tr[N << 2];
#define val(i) tr[i].val
#define l(i) tr[i].l
#define r(i) tr[i].r
#define L(i) tr[i].L
#define R(i) tr[i].R
#define sig(i) tr[i].sig
inline mdzz merge(mdzz x, mdzz y) {
mdzz res;
res.val = x.val + y.val;
res.L = x.L; res.l = x.l;
res.R = y.R; res.r = y.r;
if (x.R <= y.L) {
res.val += x.r * y.l;
if (x.sig)res.l += y.l;
if (y.sig)res.r += x.r;
res.sig = x.sig & y.sig;
}
else res.sig = 0;
return res;
}
inline void build(int now, int lt, int rt) {
if (lt == rt) {
tr[now] = (mdzz){1, 1, 1, a[lt], a[lt], 1};
return ;
}
int mid = (lt + rt) >> 1;
build(now << 1, lt, mid);
build(now << 1 | 1, mid + 1, rt);
tr[now] = merge(tr[now << 1], tr[now << 1 | 1]);
}
inline void modify(int now, int lt, int rt, int it, ll k) {
if (lt == rt) {
tr[now] = (mdzz){1, 1, 1, k, k, 1};
return ;
}
int mid = (lt + rt) >> 1;
if (it <= mid) modify(now << 1, lt, mid, it, k);
else modify(now << 1 | 1, mid + 1, rt, it, k);
tr[now] = merge(tr[now << 1], tr[now << 1 | 1]);
}
inline mdzz query(int now, int lt, int rt, int ls, int rs) {
if (ls <= lt && rt <= rs) return tr[now];
int mid = (lt + rt) >> 1;
if (rs <= mid) return query(now << 1, lt, mid, ls, rs);
if (ls > mid) return query(now << 1 | 1, mid + 1, rt, ls, rs);
mdzz res = query(now << 1, lt, mid, ls, rs);
mdzz ret = query(now << 1 | 1, mid + 1, rt, ls, rs);
return merge(res, ret);
}
}seg;
inline void mian() {
int opt = read(), x = read(), y = read();
if (opt == 1) seg.modify(1, 1, n, x, y);
else printf("%lld\n", seg.query(1, 1, n, x, y).val);
}
int main() {
n = read(); q = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
seg.build(1, 1, n);
while (q --) mian();
return 0;
}
F
先看什么情況下無解。
對於一個被標記的點,如果周圍的未被標記的點個數為奇數的話,必定無解。
那么剩下就有0個,2個,4個的可能。
0個的時候,就填0,2個的話兩個點要么1,要么4,二分圖染色就行了。
對於4個的情況,不能直接二分圖了,也不能隨便連邊,萬一有可能就把可能的答案連沒了呢。。
那么怎么去二分圖連邊呢??
再來想什么情況下無解,自然是二分圖出現了奇環。
又因為我們連邊的條件,所以會出現這兩類邊:
-
(x, y) <==> (x + 2, y) 或 (x, y + 2)
-
(x, y) <==> (x + 1, y + 1)
且注意到第二種邊一定要求有偶數條才能連成環,因為二類邊坐標每次加減1,第一條邊是怎么都沒法抵消。
所以只有可能是第一類邊的問題,所以意思就是,只要一個環,第一類邊數量出了問題,整個就一定無解了。
所以按照這個思路,對於上文說的4個的情況,我們連邊的思路就是斜着連邊,連兩條就行了。
證明的話可以參照 Editorial 的說法。(是我不會告訴你我是因為不會才這樣的)
#include <bits/stdc++.h>
#define id(i, j) (i - 1) * m + j
using namespace std;
typedef long long ll;
const int N = 5e2 + 10;
const int xx[] = {-1, 0, 1, 0};
const int yy[] = {0, -1, 0, 1};
int n, m, con[5], cnt;
int fst[N * N], tot;
int col[N * N], val[N * N];
char ch[N][N];
struct edge {
int nxt, to;
}e[N * N << 3];
inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void add(int u, int v) {
e[++tot] = (edge){fst[u], v};
fst[u] = tot;
}
inline void dfs(int u, int fa) {
for (int i = fst[u]; i ; i = e[i].nxt) {
int v = e[i].to;
if (v == fa) continue;
if (col[v] == -1) {
col[v] = col[u] ^ 1;
dfs(v, u);
}
else {
if (!(col[u] ^ col[v])){
printf("NO\n"); exit(0);
}
}
}
}
int main() {
memset(col, -1, sizeof(col));
n = read(); m = read();
for (int i = 1; i <= n; ++i) {
scanf("%s", ch[i] + 1);
}
for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) {
if (ch[i][j] == 'X') {
cnt = 0;
for (int k = 0; k < 4; ++k) {
int u = i + xx[k], v = j + yy[k];
if (u < 1 || v < 1 || u > n || v > m) continue;
if (ch[u][v] == '.') con[++cnt] = id(u, v);
}
if (cnt & 1) {
printf("NO\n");
return 0;
}
else val[id(i, j)] = cnt / 2 * 5;
//5 = 4 + 1
for (int i = 2; i <= cnt; i += 2){
add(con[i - 1], con[i]);
add(con[i], con[i - 1]);
}
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (col[id(i, j)] == -1) {
col[id(i, j)] = 0; dfs(id(i, j), 0);
}
}
}
printf("YES\n");
for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) {
printf("%d", (ch[i][j] == 'X')?(val[id(i, j)]):(col[id(i, j)] * 3 + 1));
if (j != m) printf(" "); else printf("\n");
}
return 0;
}
Educational Codeforces Round 114
賽時:3/6(wtcl)
A
顯然構造。
先把起始字符串構造成:()()()... 這樣的
然后從第二個和倒數第二個開始向里每次把“(”變為“)”,“)”變為“(”。
因為是從第二個開始的,所以前面先少個“)”,而多出來個“(”,所以能讓第三個的“)”匹配的上,之后的類似。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3+10;
int t, n;
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline void mian() {
n = read();
string s;
for (int i = 1; i <= n * 2; ++i) {
if (i & 1) s += '(';
else s += ')';
}
cout << s << endl;
for (int i = 1; i < n; ++i) {
if (s[i] == '(') {
s[i] = ')'; s[n * 2 - i - 1] = '(';
}
else {
s[i] = '('; s[n * 2 - i - 1] = ')';
}
cout << s << endl;
}
}
int main(){
t = read();
while (t--) mian();
return 0;
}
B
找上下邊界。
下邊界然容易找,最大答案就是這樣:AA..BB..CC..的答案。
相對的,我們就應該去找答案最小的時候,
我們把三個數字看成三條邊,如果能夠成三角形,兩條較小邊可以嵌入最大邊里面,由於兩較小邊互不影響,所以可以。
找這個方法,如果兩較小邊嵌入最大邊過后,最大邊剩下的點相鄰的個數就是最小值了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3+10;
int t, a[3], n;
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline void mian() {
a[0] = read(); a[1] = read(); a[2] = read(); n = read();
sort(a, a + 3);
if (a[0] + a[1] + a[2] - 3 >= n && n >= a[2] - a[1] - a[0] - 1) printf("YES\n");
else printf("NO\n");
}
int main(){
t = read();
while (t--) mian();
return 0;
}
C
無思維難度題。
跟着題目走,二分找第一個比龍牛的騎士和第一個比龍菜的騎士,算一下答案就可以了。
考慮正確性:顯然呀。因為第一個比龍牛的騎士不需要金幣,第二個比龍牛的騎士一樣,但城堡里的需要的金幣不一定一樣。
所以第二個比龍牛的騎士比第一個比龍牛的騎士不會更優。第一個比龍菜的騎士同理。
(說了一堆廢話。。)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
ll n, a[N], b[N], num, m, x, y;
ll pos1, pos2, ans1, ans2;
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline void mian() {
x = read(); y = read();
pos1 = lower_bound(a, a + n, x) - a;
pos2 = lower_bound(b, b + n, x, greater<ll>()) - b;
if (pos1 == n) pos1--;
if (pos2 == n) pos2--;
ans1 = max(y - (num - a[pos1]), 0ll) + max(x - a[pos1], 0ll);
ans2 = max(y - (num - b[pos2]), 0ll) + max(x - b[pos2], 0ll);
printf("%lld\n", min(ans1, ans2));
}
inline bool cmp(ll a, ll b) {
return a > b;
}
int main(){
n = read();
for (int i = 0; i < n; ++i) {
a[i] = read(); b[i] = a[i]; num += a[i];
}
sort(a, a + n);
sort(b, b + n, cmp);
m = read();
while (m--) mian();
return 0;
}
D
遇事不行,map一定嘚行。。
(其實我用的set。。)
其實思想上很暴力,就是把ban掉的放在一起,自己從每行最末尾的地方開始搜,
如果是ban掉的就加n個新方案,分別是原基礎上在每一行往前一個。
由於要最大值,所以全部丟到set里面。
復雜度的話因為一共就\(10^5\)個ban掉的方案,所以時間不會爆炸。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n, c[12], a[12][N], m, b[N];
set<vector<int> > q, it;
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
int main() {
n = read();
vector<int> d(n + 1), f(n + 1);
for (int i = 1; i <= n; ++i) {
c[i] = read();
for (int j = 1; j <= c[i]; ++j) {
a[i][j] = read();
}
}
for (int i = 1; i <= n; ++i) {
d[i] = c[i]; d[0] += a[i][d[i]];
}
q.insert(d);
m = read();
while (m--) {
d[0] = 0;
for (int i = 1; i <= n; ++i) {
b[i] = read();
d[i] = b[i]; d[0] += a[i][d[i]];
}
it.insert(d);
}
while (233) {
d = *--q.end();
q.erase(--q.end());
if (!it.count(d)) {
for (int i = 1; i <= n; ++i) {
printf("%d ", d[i]);
}
return 0;
}
else {
for (int i = 1; i <= n; ++i) {
if (d[i] != 1) {
f = d; --f[i];
f[0] += a[i][f[i]] - a[i][f[i] + 1];
q.insert(f);
}
}
}
}
return 0;
}
E(咕)
F(咕)
Codeforces Round #745 div2
賽時:3/6(wtcl)
A
暫且定義一個數列的整齊度為\(\sum_{i = 1}^{n - 1}[p_i < p_{i + 1}]\)
因為顯然一個長度為2n的數列最大整齊度為2n - 1。
又顯然一個整齊度為x的數列整體反轉的話整齊度就是2n - 1 - x。
所以所有整齊度小於n的數列,反轉后整齊度不小於n。
欸,這不正好覆蓋所有數列且沒有交集嗎,那答案不就是(2n)! / 2嗎!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
const ll mod = 1e9 + 7;
const ll inv2 = 5e8 + 4;
ll t, n, a[N];
inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void mian() {
n = read() * 2ll;
printf("%lld\n", (inv2 * a[n]) % mod);
}
int main() {
a[0] = 1;
for (ll i = 1; i < N; ++i) {
a[i] = (a[i - 1] * i) % mod;
}
t = read();
while (t--) mian();
return 0;
}
B
不得不說出題人是存心想要害死我們。。(邊有可能不合法。。)
只考慮邊數在\([n - 1,(n - 1) * n / 2]\)時的構造方案。(其他的都無解呀。。)
樹的直徑最小,就讓數看起來越胖與好咯。
很自然的就想到了菊花圖,直徑直接就來到了2。
那么再有什么改變的話,也只可能是在完全圖的時候,任意兩點間有邊,所以直徑為1。
就這兩種直徑,搞定。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t, n, m, k;
inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void mian() {
n = read(); m = read(); k = read() - 1;
if (n - 1 == m) {
if (n >= 3) {
if (k <= 2) printf("NO\n");
else printf("YES\n");
}
else if (n == 2) {
if (k <= 1) printf("NO\n");
else printf("YES\n");
}
else {
if (k <= 0) printf("NO\n");
else printf("YES\n");
}
}
else if (n - 1 < m) {
if (n >= 3) {
ll mor = m - (n - 1);
ll ned = (n - 1) * (n - 2) / 2;
if (mor < ned) {
if (k <= 2) printf("NO\n");
else printf("YES\n");
}
else if (mor == ned) {
if (k <= 1) printf("NO\n");
else printf("YES\n");
}
else printf("NO\n");
}
else if (n == 2) {
printf("NO\n");
}
else printf("NO\n");
}
else {
printf("NO\n");
}
}
int main() {
t = read();
while (t--) mian();
return 0;
}
C
首先最暴力的就是\(\small O(n^2m^2)\)枚舉兩個點加\(\small O(nm)\)統計答案,統共\(\small O(n^3m^3)\)。
加個前綴和,\(\small O(n^2m^2)\)。
考慮怎么優化,枚舉這些已經不能再優了,但是其實和前綴和思想一樣,我們可以在計算許多矩形的時候利用一下前面已有的答案。
但我們還是先定上下邊界,然后從左往右計算定好右邊界的時候的最小答案。
(我是從右往左,因為1開始有點暈,n開始方便一點,但講的時候還是從左往右)
然后我們可以先算最左邊最小的矩形,然后發現向右擴展的時候有點麻煩,所以我們干脆先撇掉最右邊這一列,這樣擴展起來就好多了,
這是我們就只需要算兩個東西:
1.以右邊界為起點的最小矩形答案
2.以右邊界為起點的其它矩形最小答案
顯然情況2就是前面傳下來的答案,直接就可以用,一也可以\(\small O(1)\)算。
最后再全部加上最右邊這一列的貢獻就可以了。
時間復雜度\(\small O(n^3)\)級。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 410, INF = 1e9;
ll t, n, m, ans, cnt[N];
ll pos[N][N], qzh[N][N];
string s[N];
inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline ll calc(int x1, int y1, int x2, int y2) {
ll res = qzh[x2][y2] - qzh[x1 - 1][y2];
ll ret = qzh[x2][y1 - 1] - qzh[x1 - 1][y1 - 1];
return res - ret;
}
inline void mian() {
n = read(); m = read(); ans = INF;
for (int i = 1; i <= n; ++i) {
getline(cin, s[i]);
for (int j = 1; j <= m; ++j) {
pos[i][j] = s[i][j - 1] - '0';
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
qzh[i][j] = pos[i][j];
qzh[i][j] += qzh[i - 1][j] + qzh[i][j - 1] - qzh[i - 1][j - 1];
}
}
for (int l = 1; l <= m; ++l) {
for (int r = l + 3; r <= m; ++r) {
cnt[n - 3] = r - l - 1 - calc(n, l + 1, n, r - 1) + 6;
cnt[n - 3] += calc(n - 3, l + 1, n - 1, r - 1);
cnt[n - 3] -= calc(n - 3, l, n - 1, l) + calc(n - 3, r, n - 1, r);
for (int i = n - 4; i >= 1; --i) {
cnt[i] = r - l - 1 - calc(i + 3, l + 1, i + 3, r - 1) + 6;
cnt[i] += calc(i, l + 1, i + 2, r - 1);
cnt[i] -= calc(i, l, i + 2, l) + calc(i, r, i + 2, r);
ll num = (!pos[i][l]) + (!pos[i][r]);
cnt[i] = min(cnt[i], cnt[i + 1] + calc(i, l + 1, i, r - 1) + num);
}
for (int i = 1; i <= n - 4; ++i) {
ans = min(ans, cnt[i + 1] + r - l - 1 - calc(i, l + 1, i, r - 1));
}
}
}
printf("%lld\n", ans);
}
int main() {
t = read();
while (t--) mian();
return 0;
}
D(咕)
E
直接做的話,可以發現兩種操作的時間比較懸殊。
雖然兩者分開來看,時間都已經到了極限,但如果我們把他們合在一起看。
我們就會想到考慮能不能把兩者的時間平衡下來,從而達到降低時間復雜度的效果。
這就是分塊的思想,我們能接受的小塊,可以就按暴力的做法。但是大塊就要在修改的時候處理好,而不同一是 \(\small O(N)\) 查詢。
又注意到這些工作的機器是有周期性的,一會工作,一會修理,所以我們可以按照周期分兩類。
設我們能接受的“小塊”,最大周期為 \(\small P\) 。
那么在加入一個車的時候:
如果周期大於 \(\small P\) ,那么整個 \(\small M\) 個時段里面他工作的時段不會超過 \(\small M/P\) ,所以添加,刪除的時候修改一個差分數組,標記每個時間段首尾就可以了,時間復雜度 \(\small O(M/P)\) 。
如果周期小於 \(\small P\) ,雖然不可能再向剛剛那樣暴力修改了。但是我們可以知道對於一個時刻,我們可以判斷它是否在某個機器的工作時段內。所以對於一個機器的周期,只要標記好開始的時間,之后的所有時段,查詢時,只要枚舉這些周期就能一網打盡。因為規定了周期是小於 \(\small P\) 的,所以時間復雜度 \(\small O(P)\) 。
綜上,時間復雜度總和是 \(\small O(M/P + P)\) ,現在平衡兩者的時間的能力已經到了我們手上,此時 \(\small P\) 取 \(\small \sqrt{M}\) 時就是最優的,總時間復雜度也就是 \(\small O(M\sqrt{M})\) 。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10, M = 457;
int n, m, sq, c[M][M], pos[N], num[N], ans;
struct mdzz {
int x, y, sum;
} p[N];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void mian(int i) {
int opt = read(), k =read();
if (opt == 1) {
pos[k] = i;
if (p[k].sum > sq) {
for (int j = i; ;) {
j += p[k].x; if (j > m) break;
++num[j];
j += p[k].y; if (j > m) break;
--num[j];
}
}
else {
--c[p[k].sum][i % p[k].sum];
++c[p[k].sum][(i + p[k].x) % p[k].sum];
++ans;
}
}
else {
int lth = i - pos[k], now = lth % p[k].sum;
if (now > p[k].x || now == 0) --ans;
if (p[k].sum > sq) {
for (int j = pos[k]; ;) {
j += p[k].x; if (j > m) break;
--num[j];
j += p[k].y; if (j > m) break;
++num[j];
}
}
else {
++c[p[k].sum][pos[k] % p[k].sum];
--c[p[k].sum][(pos[k] + p[k].x) % p[k].sum];
}
}
ans += num[i];
for (int j = 1; j <= sq; ++j) {
ans += c[j][i % j];
}
printf("%d\n", ans);
}
int main() {
n = read(); m = read(); sq = sqrt(m);
for (int i = 1; i <= n; ++i) {
p[i] = (mdzz) {read(), read()};
p[i].sum = p[i].x + p[i].y;
}
for (int i = 1; i <= m; ++i) mian(i);
return 0;
}
F
式子看着很煩,沒什么性質,所以轉化一下:
可以想到后面那個循環里面,包括 \(i\) 和 \(j\) , \(1\) 到 \(m\) 均被提到了 \(m - 1\) 次,所以前面那坨可以放進后面了。
為了美觀我們假設 \(b_i\) 為升序。(只是為了美觀而已。。)
后面那托東西,越看越熟悉,這有點像樹上兩點間的距離呀!
所以可以試着向樹發展,現在就要想怎么構造這個樹。
\(\min_{k = b_i}^{b_j} 2\cdot a_k\),這玩意換成文字描述的話,就表示數組上兩個點坐標中間的最小值。
意思就是要兩個點的 \(lca\) 就是上面那玩意,哦,笛卡爾樹!
那么還要求距離是 \(a_{b_i} + a_{b_j} - \min_{k = b_i}^{b_j} 2\cdot a_k\) 的話,邊權要怎么設呢?
因為最后總邊權只跟 \(lca\) 和那兩個點的 \(a_i\) 有關,而中間的點毫無貢獻,所以邊權多半是個差結構,用來抵消中間點的貢獻。
暫且就認為邊權為所連接的兩點 \(a_i\) 的差,式子化就是 \(w_{u, v} = a_u - a_v\) 。
手摸一下,兩點 \(i, j\) 距離就是 \(a_{i} + a_{j} - \min_{k = i}^{j} 2\cdot a_k\) 。
這不就好起來了嗎!
可以直接建樹了,然后問題轉化成一棵樹,求任選 \(m\) 個點,使得點兩兩距離之和最大。
一個比較經典的 DP 模型(?), \(f_{i, j}\) 表示 \(i\) 子樹下選 \(j\) 個點的最大值。
然后從笛卡爾樹的樹根(就是 \(a_i\) 最小的那個點)開始 dfs ,記住每次對於一條邊,轉移的時候算一下這條邊被經過了幾次就行了。
(注:下面還有一點喲。。)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4e3 + 10;
int n, m, a[N], sta[N], top, ls[N], rs[N];
int fst[N], tot, rt = 1, siz[N];
ll f[N][N];
struct edge {int nxt, to, val;} e[N];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void add(int u, int v, int w) {
e[++tot] = (edge) {fst[u], v, w};
fst[u] = tot;
}
inline void Cartesian() {
sta[top = 1] = 1;
for (int i = 2; i <= n; ++i) {
while (a[sta[top]] > a[i] && top) --top;
if (!top) ls[i] = sta[top + 1];
else {
ls[i] = rs[sta[top]];
rs[sta[top]] = i;
}
sta[++top] = i;
}
}
inline void dfs(int u) {
siz[u] = 1;
for (int i = fst[u]; i; i = e[i].nxt) {
int v = e[i].to, w = e[i].val;
dfs(v);
int s1 = min(m, siz[u]);
int s2 = min(m, siz[v]);
for (int j = s1; j >= 0; --j) {
for (int k = s2; k >= 0; --k) {
ll anp = f[u][j] + f[v][k];
ll rep = 1ll * k * (m - k);
f[u][j + k] = max(f[u][j + k], anp + rep * w);
}
}
siz[u] += siz[v];
}
}
int main() {
n = read(); m = read();
for (int i = 1; i <= n; ++i) a[i] = read();
Cartesian();
for (int i = 1; i <= n; ++i) {
if (ls[i]) add(i, ls[i], a[ls[i]] - a[i]);
if (rs[i]) add(i, rs[i], a[rs[i]] - a[i]);
if (a[rt] > a[i]) rt = i;
}
dfs(rt);
printf("%lld\n", f[rt][m]);
return 0;
}
其實寫出來后,直觀感覺時間復雜度是 \(O(n \cdot m^ 2)\) 而非 \(O(n ^ 2)\),並不能過。
但其實我們是邊合並一個子樹,邊計算的。所以就意思是對於一條邊,我們本來是枚舉的兩個點的 \(siz\) 。相當於每次兩個集合所有點之間相互建邊,然后形成一個更大的集合,再接着枚舉下一條邊的時候,繼續與其他集合合並。
所以宏觀上來看,整個過程就是 \(n\) 個點兩兩連邊。所以時間復雜度就是 \(O(n ^ 2)\) ,能過掉此題。
Codeforces Round #746 div2
賽時:1/6(還好用的小號。。)
A
一個武器不能反復用,那就把攻擊力最大的和第二大的武器輪換着用。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e3 + 10;
int t, n, h, a[N], sig, tot, m1, m2, num;
inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void mian() {
n = read(); h = read();
num = m1 = m2 = sig = tot = 0;
for (int i = 1; i <= n; ++i) {
a[i] = read();
if (a[i] > m1) {
m2 = m1;
m1 = a[i];
}
else if (a[i] > m2) {
m2 = a[i];
}
}
num = h / (m1 + m2);
h -= num * (m1 + m2);
if (h == 0) {
tot = 2 * num;
}
else if (h <= m1) {
tot = 2 * num + 1;
}
else tot = 2 * num + 2;
printf("%d\n", tot);
}
int main() {
t = read();
while (t--) mian();
return 0;
}
B
題目要求能不能讓一個序列有序且操作的點對距離不小於k。
大力分類討論:
自身有序,可以完成;
k <= (n / 2),因為這樣任意兩個點都能直接或間接地互換,所以可以完成;
剩下的點只要無法間接互換,只要不再他排序后的位置上,就不能完成了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int t, n, x, a[N], b[N], sig, vis;
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void mian() {
n = read(); x = read(); vis = sig = 0;
for (int i = 1; i <= n; ++i) {
b[i] = a[i] = read();
if (i != 1) {
if (a[i - 1] > a[i]) vis = 1;
}
}
sort(b + 1, b + 1 + n);
if (!vis) {
printf("YES\n");
return ;
}
if (x <= (n / 2)) {
printf("YES\n");
}
else {
for (int i = n - x + 1; i <= x; ++i) {
if (a[i] != b[i]) {sig = 1; break;}
}
if (sig) printf("NO\n");
else printf("YES\n");
}
}
int main() {
t = read();
while (t--) mian();
return 0;
}
C
問是否存在將一棵樹划分為最多 k 個連通塊,且這些連通塊內點權的異或和相等。
分開來看,如果 k 為偶數,那么一定要求所有的異或和為零,參考的是 \(x\ xor\ x\ =\ 0\) 。
如果 k 為奇數,這樣的話每個連通塊異或和要為異或和,大力 dfs ,然后判一下能不能分成至少三塊,因為分成三塊以后,剩下的可以通過兩兩抵消的方式還原出來。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int t, n, k, cnt;
ll a[N], w[N], sum;
vector<int> e[N];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void dfs(int u, int fa) {
w[u] = a[u];
for (int i = 0; i < (int)e[u].size(); ++i) {
int v = e[u][i];
if (v == fa) continue;
dfs(v, u); w[u] ^= w[v];
}
if (w[u] == sum) ++cnt, w[u] = 0;
}
inline void mian() {
n = read(); k = read(); cnt = sum = 0;
for (int i = 1; i <= n; ++i) {
a[i] = read(); sum ^= a[i];
e[i].clear();
}
for (int i = 1; i < n; ++i) {
int u = read(), v = read();
e[u].push_back(v); e[v].push_back(u);
}
if (!sum) {printf("YES\n"); return ;}
if (k == 2) {printf("NO\n"); return ;}
dfs(1, 0);
if (cnt < 2) printf("NO\n");
else printf("YES\n");
}
int main() {
t = read();
while (t--) mian();
return 0;
}
D
一道交互題。
眾所周知, gcd 隨數的增加只會變得不增,所以只要把樹編個序,上二分就可以了。
那么怎么去編序號呢?條件就是這個區間內的點一定相互直接聯通,因為如果不直接連通的話,最后即使只剩兩點,也無法確定是那條邊。
不難想到這種序列就是歐拉序,其實同樣的道理, dfn 序也是可以的。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3 + 10;
int n, dfn[N], tim, fth[N], num;
vector<int> e[N];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline void dfs(int u, int fa) {
dfn[++tim] = u; fth[u] = fa;
for (int v : e[u]) {
if (v == fa) continue;
dfs(v, u);
}
}
inline int query(int m) {
cout << "? " << m;
for (int i = m; i >= 1; --i) {
cout << " " << dfn[i];
}
cout << endl;
int x = read();
return x;
}
int main() {
n = read();
for (int i = 1; i < n; ++i) {
int u = read(), v = read();
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1, 0);
num = query(n);
int lt = 2, rt = n - 1, ans = lt;
while (lt <= rt) {
int mid = (lt + rt) >> 1;
if (num == query(mid)) rt = mid - 1;
else lt = ans = mid + 1;
}
cout << "! " << dfn[ans] << " " << fth[dfn[ans]] << endl;
return 0;
}
E
如果要 並 嚴格大於 異或,並 肯定要二進制下那一位全是1,所以要 異或 為零的話,數量必須是偶數。
所以現在就是要 異或 和 並 的值二進制下從高到低第一個不相等的位是上述情況。
那么就可以枚舉二進制位,然后因為是找的子串,所以可以記錄狀態,然后暴力 \(\small O(N)\) 做。
整個就是 \(\small O(N\log N)\) 。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n, a[N], num[N], sum, ans;
bool vis[N];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
int main() {
n = read();
for (int i = 1; i <= n; ++i) {
a[i] = read();
}
for (int it = 0; it <= 20; ++it) {
memset(num, 0, sizeof(num));
num[0] = 1; sum = 0;
for (int i = 1; i <= n; ++i) {
if (a[i] & (1 << it)) {
sum ^= (a[i] >> it);
if (!num[sum]) num[sum] = i + 1;
else ans = max(ans, i - num[sum] + 1);
}
else {
memset(num, 0, sizeof(num));
num[0] = i + 1; sum = 0;
}
}
}
printf("%d\n", ans);
return 0;
}
F1
直接硬做這道題好像暴力枚舉暴力計算,都是個大問題。
如果來想想什么情況下可以把一整個矩形“濃縮”到一個點上,自然就想到了差分,省去了枚舉。
所以我們將每個點設成它,左,下,左下的黑白數量,那么如果要將一個矩形取反,按照差分的套路,就是要反轉右下角,左下角的左邊,右上角的上邊,左上角的左上邊四個點。
形式化點,就是:
-
\(a_{i, j} = w_{i, j} \oplus w_{i + 1, j} \oplus w_{i, j + 1} \oplus w_{i + 1, j + 1}\)
-
反轉矩形 \(((i, j) ,\ (k, l))\) \(\Longrightarrow\) 反轉 \(a_{i - 1, j - 1} \ a_{k, j - 1} \ a_{i - 1, l} \ a_{k, l}\)
但這里一共有四種操作,又怎么取舍呢??
看到2,3操作都分別可以用2個1操作完成,所以不去動他們。
4操作可以用4個1操作完成,所以可能存在4操作更優的可能。
同時注意到,1操作只對應的取反 \(a_{n, m}\) ,所以大部分 \(a_{i, j}\) 都是1操作干的。
再同時如果有個4操作對應的矩形四個關鍵點都需要取反,這樣可以比四個1操作便宜。
注意到兩個4操作會讓 \(a_{n, m}\) 不變,此時至多貢獻了六個點,比只做1操作不優,所以只需要找有沒有這樣的矩形就行,一個就夠了。
那這道題就完了!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 520;
int n, m, a[N][N], ans;
char s[N];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
int main() {
n = read(); m = read();
for (int i = 1; i <= n; ++i) {
scanf("%s", s + 1);
for (int j = 1; j <= m; ++j) {
a[i][j] = (s[j] == 'B');
}
}
for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) {
a[i][j] ^= a[i + 1][j] ^ a[i][j + 1] ^ a[i + 1][j + 1];
}
for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) ans += a[i][j];
for (int i = 1; i < n; ++i) for (int j = 1; j < m; ++j) {
if ((a[i][j] && a[i][m] && a[n][j] && a[n][m])) {printf("%d\n", ans - 1); return 0;}
}
printf("%d\n", ans);
return 0;
}
F2
有了F1的鋪墊,就不至於那么手足無措了。
同樣的道理,2,3操作不要,但是4操作又變便宜了,可能的操作又變多了。
那么又有什么新情況會可能更便宜呢。
先不看 \(a_{n, m}\) ,假如4操作對應的一個矩形有一個點不需要反轉,對這種矩形做4操作的話。
要么通過另一個4操作補回來,要么在單獨做個1操作。
前者涉及5個點,其中至少1個已滿足,所以至多完成了4個點,花費4,等於四個1操作。
后者涉及4個點(因為 \(a_{n, m}\) 只反轉了一次),其中至少1個點已滿足,所以至多完成3個點,花費3,等於三個1操作。
所以只有4操作對應的矩形三個點都要反轉才有可能有貢獻。
但是眾多矩形,可能會出現有些矩形會有兩個點重合(其中一個是 \(a_{n, m}\) ),這種情況下同上一樣的分析,也一樣可以被1操作取代,所以只有互不重合的矩形才能有貢獻,所以要保證盡可能多的配對數量,用二分圖匹配。
最后再單獨把 \(a_{n, m}\) 是否需要取反判一下就完成了!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3 + 10;
int n, m, a[N][N], vis[N], mch[N], ans, _ans;
char s[N];
vector<int> e[N];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
return s * w;
}
inline bool Hungary(int u, int zs) {
if (vis[u] == zs) return 0;
vis[u] = zs;
for (int v : e[u]) {
if ((mch[v] == 0) || Hungary(mch[v], zs)) {
mch[v] = u; return 1;
}
}
return 0;
}
int main() {
n = read(); m = read();
for (int i = 1; i <= n; ++i) {
scanf("%s", s + 1);
for (int j = 1; j <= m; ++j) {
a[i][j] = (s[j] == 'B');
}
}
for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) {
a[i][j] ^= a[i + 1][j] ^ a[i][j + 1] ^ a[i + 1][j + 1];
}
for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) ans += a[i][j];
for (int i = 1; i < n; ++i) for (int j = 1; j < m; ++j) {
if (a[i][j] && a[i][m] && a[n][j]) {
e[i].push_back(j + n); e[j + n].push_back(i);
}
}
for (int i = 1; i < n; ++i) if (Hungary(i, i)) ++_ans;
for (int i = 1; i < m; ++i) if (Hungary(i + n, i + n)) ++_ans;
_ans /= 2;
ans -= _ans + a[n][m] - (a[n][m] ^ (_ans & 1));
printf("%d\n", ans);
return 0;
}