AGC027解题记录
到现在还不会单调栈和单调队列......
A:贪心
sort一遍之后直接贪心完事。
B:贪心
在B题就被残忍杀害了......
首先不难发现每一次出去收集一组垃圾,一定是先走到最远的那个,再依次走回来收集。
设这一组的垃圾 \(S=\{ p_{1},p_{2}...p_{n} \}\)
那么这一次的总花费(不考虑收集垃圾和扔垃圾)
\(
\begin{aligned}
&x_{n}+(n+1)^2x_{p_{1}}+\sum^{n-1}_{i=1}(x_{p_{n-i+1}}-x_{p_{n-i}})(i+1)^2\\
=&5x_{p_{n}}+\sum_{i=1}^{n-1}(2i+1)x_{p_{n-i}}
\end{aligned}
\)
可以发现这个序列的系数是单调不减的。
这里可以得到一个简单贪心,即把 \(5\) 这个系数分配给最大的 \(x\) ,把 \(7\) 这个系数分配给次大的......
于是可以枚举捡 \(k\) 次垃圾,求出最小花费,最后取 \(\min\) 。
这个过程可以用前缀和优化,是个调和级数,所以总复杂度 \(O(n\log n)\) 。
ps:由于答案可能会爆ll,所以要先算出一个不会爆ll的上界,然后在计算过程中如果比这个上界大就直接break。
Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
bool f=true;ll x=0;
register char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(f) return x;
return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
const int MAXN=2e5+7;
ll n,d,x[MAXN],ans,sum[MAXN],p[MAXN];
int main(){
// freopen("rand.in","r",stdin);
// freopen("1.out","w",stdout);
n=read(),d=read();
p[1]=5;
for(ri i=2;i<=n;++i) p[i]=2*i+1;
for(ri i=1;i<=n;++i){
x[i]=read();
ans+=2*d+x[i]*5;
sum[i]=sum[i-1]+x[i];
}
for(ri k=1,B;k<n;++k){
ll res=k*d;
//B=(n+k-1)/k;
for(ri j=n,cnt=1;j>0;j-=k){
res+=(sum[j]-sum[max(j-k,0)])*p[cnt++];
if(res>ans)
break;
}
ans=min(ans,res);
}
print(ans+n*d);
return 0;
}
C:拓扑
XJ,你又搬原题了
那么盲猜一波结论,一定是最后出现了一个联通块,联通块内的点都是既能到 \(A\) 又能到 \(B\)。
这个显然是充分条件,考虑证明它的必要性。
如果存在某个点,它只能到 \(A,B\) 中的一个:
1.不存在只有经过这个点才能构造出来的字符串。
那么直接把这个点删掉不会影响答案。
2.存在只有经过这个点才能构造出的字符串。
假设这个字符串是 \(S\) ,那么这个图就一定无法构造出 \(S+A\) 或者 \(S+B\),是不合法的。
综述,这个条件也是一个必要条件。
因此,模拟删除的过程,根据最后是否有点剩下来判断图是否合法。
这个过程可以用类似拓扑的方法实现。
Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
bool f=true;ll x=0;
register char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(f) return x;
return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
int n,m;
const int MAXN=2e5+7;
vector<int> g[MAXN];
char s[MAXN];
int dig[MAXN][2];
int mark[MAXN];
int main(){
n=read(),m=read();
scanf("%s",s+1);
for(ri i=1;i<=n;++i) s[i]-='A';
for(ri i=1;i<=m;++i){
int u=read(),v=read();
dig[u][s[v]]++;
dig[v][s[u]]++;
g[u].push_back(v);
g[v].push_back(u);
}
queue<int> q;
for(ri i=1;i<=n;++i)
if(!dig[i][0]||!dig[i][1]) q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
if(mark[u]) continue;
mark[u]=1;
for(ri i=0;i<g[u].size();++i){
int v=g[u][i];
if(!(--dig[v][s[u]])) q.push(v);
}
}
for(ri i=1;i<=n;++i){
if(!mark[i]) return !puts("Yes");
}
puts("No");
return 0;
}
D:构造
挺巧妙的一道构造最后还是没想出来,贺题解了。
首先,非常一眼地可以发现对这个矩阵黑白染色之后相同颜色的点之间不会有影响。
于是,可以钦定其中一种颜色用来填小的数字,另一种颜色填大的数字。
先提出一种比较naive我能想到的构造方法:往黑格中填质数,白格为周围四个黑格的乘积+1。
这个的正确性显然,也能保证数字各不相同,然而,无法保证 \(a_{i,j}\leq 10^15\)。
可以发现这个构造的过程非常浪费,\(lcm\)这个性质并没有利用起来,而为了保证数字各不相同只需要这个数能拆成两个质数乘积+1的形式就可以了。
于是换一种构造方式:给每一条黑色对角线分配一个质数,黑格上的点就是相交的两条黑色对角线的乘积。
这个的正确性也非常显然,而且只要稍微地贪心一下,让对角线尽量地小配大,就能保证 \(a_{i,j} \leq 10^15\) 了。
对角线的下标非常恶心
Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
bool f=true;ll x=0;
register char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(f) return x;
return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
int n=read(),m=read();
const int MAXN=2048;
char s[MAXN];
int a[MAXN][MAXN],b[MAXN][MAXN],r[MAXN],sta[MAXN],top,sum[MAXN];
ll ans;
int main(){
ans=max(m,n);
for(ri i=1;i<=n;++i){
scanf("%s",s+1);
for(ri j=1;j<=m;++j){
a[i][j]=s[j]=='#';
}
}
for(ri i=1;i<n;++i){
for(ri j=1;j<m;++j){
b[i][j]=1^a[i][j]^a[i][j+1]^a[i+1][j]^a[i+1][j+1];
}
}
n--,m--;
sum[0]=sum[m+1]=-1;
for(ri i=1;i<=n;++i){
memset(r,0,sizeof(r));
for(ri j=1;j<=m;++j){
if(b[i][j]) ++sum[j];
else sum[j]=0;
}
top=0;
sta[++top]=m+1;
for(ri j=m;j;--j){
while(sum[sta[top]]>=sum[j]) --top;
r[j]=sta[top]-1;
sta[++top]=j;
}
top=0;
sta[++top]=0;
for(ri j=1;j<=m;++j){
while(sum[sta[top]]>=sum[j]) --top;
ans=max(ans,(sum[j]+1)*(r[j]-sta[top]+1));
sta[++top]=j;
}
}
print(ans);
return 0;
}
E:贪心+字符串+DP
神仙题+1。
AT的题目看到字符串的字母变换,按照套路都要去找不变量。
令 \(a=1,b=2\) ,可以发现每次变换之后取模 \(3\) 的值是不变的。
设最后得到的字符串 \(T\),那么 \(T\) 中的每一个字符都可以对应一段连续的区间。
接下来盲猜一波结论,只要能把 \(S\) 划成若干段,每一段的值模三之后就是其对应的字符。
结论这里就不证明了不会证。
然后就可以做一个类似于子序列自动机一样的东西去匹配了。
Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
bool f=true;ll x=0;
register char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
if(f) return x;
return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
const int MAXN=1e5+7,mod=1e9+7;
ll f[MAXN][3],n,sum[MAXN],nxt[MAXN][3],lst[3];
char s[MAXN];
bool check(){
for(ri i=1;i<n;++i)
if(s[i]==s[i+1]) return 0;
return 1;
}
ll ans;
int main(){
scanf("%s",s+1);
n=strlen(s+1);
if(check()) return !puts("1");
lst[0]=n+1;
for(ri i=n;i;--i){
sum[i]=(sum[i+1]+s[i]-'a'+1)%3;
nxt[i][0]=lst[sum[i]];
nxt[i][1]=lst[(sum[i]+2)%3];
nxt[i][2]=lst[(sum[i]+1)%3];
lst[sum[i]]=i;
}
f[n+1][0]=1;
for(ri i=n;i;--i){
/*
x-y=z
y=x-z
*/
for(ri j=0;j<3;++j){
int x=nxt[i][j];
if(j) f[i][j]=(f[x][0]+f[x][1]+f[x][2])%mod;
else f[i][j]=f[x][0];
}
}
ans=(f[1][1]+f[1][2])%mod;
print(ans);
return 0;
}
F:
不会,告辞。