题目地址:https://codeforces.com/contest/1262
题意:
思路:
代码:
int main()
{
//freopen("input.txt","r",stdin);
int T=read();
while(T--)
{
int n=read(),a=1e9+5,b=-1e9-5;
REP(i,1,n)
{
int l=read(),r=read();
a=min(a,r); b=max(b,l);
}
printf("%d\n",a<=b?b-a:0);
}
return 0;
}
题意:给定一个数组q,该数组记录的是某个排列的前缀最大值,求一个满足该数组的排列,或者说明不可能满足。
思路:首先如果 q[i]1,如果 q[k]>q[k-1],那么必有 a[k]=q[k],否则让 a[k] 从小到大取没有分配过的数,这样就可以构造出来。
代码:
const int maxn=1e5+5;
int n,t,q[maxn],vis[maxn],ans[maxn];
int main()
{
//freopen("input.txt","r",stdin);
t=read();
while(t--)
{
n=read();
REP(i,1,n) q[i]=read();
ans[1]=q[1];
vis[q[1]]=1;
int k=1,flag=0;
REP(i,2,n)
{
if(q[i]<i || q[i]<q[i-1]) {flag=1; break;}
else if(q[i]>q[i-1]) ans[i]=q[i],vis[q[i]]=1;
else
{
while(k<=n && vis[k]) k++;
ans[i]=k;
vis[k++]=1;
}
}
if(flag) puts("-1");
else
{
REP(i,1,n) printf("%d ",ans[i]);
puts("");
}
memset(vis,0,sizeof(vis[0])*(n+2));
}
return 0;
}
题意:有一个长度为 n 括号序列(左括号和右括号数目一样),有一种操作可以使得连续区间翻转,求一个翻转操作方案(不要求次数最少),使得翻转之后的序列有 k 个前缀是合法的括号序列(也就是匹配)。
思路:假设 n=10,那么 k=1 的方案为 ((((()))))
,k=2 的方案为 ()(((())))
,k=3 的方案为 ()()((()))
,以此类推;故我们从前往后逐个检查括号,不满足方案的就往后找到最近的相反括号,翻转这一段区间即可。这么做复杂度为 O(n^2)。
代码:
const int maxn=2005;
int T,n,k,a[maxn],l[maxn],r[maxn],ans;
char s[maxn];
int find_next(int i)
{
int j=i+1;
while(a[j]==a[i]) j++;
return j;
}
void update(int i)
{
int j=find_next(i);
l[ans]=i; r[ans++]=j;
reverse(a+i,a+j+1);
}
int main()
{
//freopen("input.txt","r",stdin);
T=read();
while(T--)
{
n=read(),k=read(); ans=0;
scanf("%s",s+1);
REP(i,1,n) a[i]=s[i]==')';
REP(i,1,n)
{
if(i<=(k-1)*2 && (i&1) && a[i]) update(i);
else if(i<=(k-1)*2 && !(i&1) && !a[i]) update(i);
else if(i>(k-1)*2 && i<=n/2+k-1 && a[i]) update(i);
else if(i>(k-1)*2 && i>n/2+k-1 && !a[i]) update(i);
}
printf("%d\n",ans);
REP(i,0,ans-1) printf("%d %d\n",l[i],r[i]);
}
return 0;
}
题意:给定一个长度为 n(2e5)的数组,它的长度为 k 的最佳子序列定义为:子序列(不要求连续)长度为k,元素和在所有相同长度子序列中最大,满足以上条件下字典序最小的序列。给出 m(2e5)组询问,每一组询问给出一个 k i k_i ki 和 p o s i pos_i posi ,要求回答长度为 k i k_i ki 的最佳子序列的第 p o s i pos_i posi 个元素大小是多少。
思路:因为我们不可能处理出所有长度的最佳子序列的所有位置的元素,所以考虑对询问离线。要满足元素和最大并且字典序最小,我们可以对每个元素建一个二元组 ( a i , i ) (a_i,i) (ai,i),然后第一关键字降序,第二关键字升序排列,这样前 k 个数就是长度为 k 的最佳子序列要取的数(和位置),因为这既保证了和最大,也保证了优先选择 index 更小的,也就是字典序最小。
然后就可以从小到大遍历 k 了,我们还需要维护一个 Treap,用来保存已经选取的元素的 index,遍历 k 的时候同时处理长度为 k 的所有询问(每个询问就是查询一次 Treap 中的某个排名的元素)就可以了。复杂度为 O(nlogn)。
代码:
struct treap
{
int tot,root;
int *t,*num,(*ch)[2],*rd,*siz;
treap(int maxn)
{
t=new int[maxn](); num=new int[maxn]();
rd=new int[maxn](); siz=new int[maxn]();
ch=new int[maxn][2](); root=tot=0;
}
void push_up(int k) {siz[k]=siz[ch[k][0]]+siz[ch[k][1]]+num[k];}
void rotate(int &k,int d)
{
int x=ch[k][d^1];
ch[k][d^1]=ch[x][d]; ch[x][d]=k;
push_up(k); push_up(x);
k=x;
}
void insert(int &k,int x)
{
if(!k) {k=++tot; t[k]=x; num[k]=siz[k]=1; rd[k]=rand(); return;}
else if(t[k]==x) {num[k]++; siz[k]++; return;}
else
{
int d=x>t[k];
insert(ch[k][d],x);
if(rd[k]<rd[ch[k][d]]) rotate(k,d^1);
push_up(k);
}
}
void insert(int x) {insert(root,x);}
int who_ranking(int k,int r)
{
if(!k) return 0;
if(r<=siz[ch[k][0]]) return who_ranking(ch[k][0],r);
if(r>siz[ch[k][0]]+num[k]) return who_ranking(ch[k][1],r-siz[ch[k][0]]-num[k]);
return t[k];
}
int who_ranking(int r) {return who_ranking(root,r);}
};
const int maxn=2e5+5;
int n,a[maxn],k[maxn],p[maxn],ans[maxn];
struct node
{
int value,id;
bool operator < (const node x) const
{
if(value==x.value) return id<x.id;
return value>x.value;
}
}b[maxn];
vector<node> q[maxn];
int main()
{
//freopen("input.txt","r",stdin);
n=read();
treap t(n+5);
REP(i,1,n) a[i]=read(),b[i]=(node){a[i],i};
sort(b+1,b+n+1);
int m=read();
REP(i,1,m) k[i]=read(),p[i]=read(),q[k[i]].push_back((node){p[i],i});
REP(i,1,n)
{
t.insert(b[i].id);
REP(j,0,q[i].size()-1)
{
int w=q[i][j].value,id=q[i][j].id;
ans[id]=a[t.who_ranking(w)];
}
}
REP(i,1,m) printf("%d\n",ans[i]);
return 0;
}
题意:(略去背景)有一张 n*m(n*m<1e6)的地图,上面有些格点为 1,有些为 0;初始时刻为 0,每经过 1 时间,值为 1 的格点会把八个方向相邻的格点也变成 1。现在要求一个初始地图(给每个格点分配 1 或 0),使得经过时间 T,该初始地图会变成给出的地图,并且时间 T 要尽可能大。
思路:可以看出这道题的意思就是要找出最大的 k(k为奇数),使得给出的地图中每个为 1 的格点都能被某个 k*k 且全部都为 1 的正方形覆盖。首先对给出的地图求一个前缀和 s,然后我们设 fs(i, j, k) = s[i][j] - s[i-k][j] - s[i][j-k] + s[i-k][j-k](也就是以 (i, j) 为右下角,边长为 k 的正方形的权值和),考虑二分答案,对于某个边长 k,对于某个值为 1 的格点 (i, j) ,它能被上述的某个正方形覆盖,当且仅当在正方形区间 [i ~ i+k-1, j ~ j+k-1] 存在一个格点 (x, y) ,使得 fs(x, y, k) = k*k 。
这样考虑之后,我们就可以遍历数组求解了,还有要注意的就是找 (x, y) 时要更加优先更右下角的,这样可以在找一次的时候能标记更多的格点。这种做法复杂度 O(n*m*log(min(n,m)))。
代码:
int **create(int n,int m)
{
int **a=new int*[n+5];
REP(i,0,n+1) a[i]=new int[m+5];
REP(i,0,n+1) REP(j,0,m+1) a[i][j]=0;
return a;
}
const int maxn=1e6+5;
char ss[maxn];
int **a,**s,n,m;
int get(int x,int y,int k)
{
if(x<k || y<k || x>n || y>m) return 0;
return s[x][y]-s[x][y-k]-s[x-k][y]+s[x-k][y-k];
}
bool can(int k)
{
int **temp=create(n,m);
REP(i,1,n) REP(j,1,m) if(!temp[i][j] && a[i][j])
{
int ii=0,jj=0;
REP(x,i,min(i+k-1,n))
{
REP(y,j,min(j+k-1,m))
if(get(x,y,k)==k*k)
{
ii=x; jj=y;
}
}
if(!ii || !jj) return 0;
REP(x,ii-k+1,ii) REP(y,jj-k+1,jj)
{
if(!a[x][y]) return 0;
temp[x][y]=1;
}
}
return 1;
}
int main()
{
//freopen("input.txt","r",stdin);
n=read(),m=read();
a=create(n,m),s=create(n,m);
REP(i,1,n)
{
scanf("%s",ss+1);
REP(j,1,m) a[i][j]=(ss[j]=='X');
}
REP(i,1,n) REP(j,1,m) s[i][j]=s[i][j-1]+s[i-1][j]+a[i][j]-s[i-1][j-1];
int l=0,r=(min(n,m)-1)/2+1,mid;
while(l<r-1)
{
mid=(l+r)>>1;
if(can(mid*2+1)) l=mid;
else r=mid;
}
printf("%d\n",l);
int k=l*2+1;
REP(i,1,n)
{
REP(j,1,m) putchar(get(i+l,j+l,k)==k*k?'X':'.');
puts("");
}
return 0;
}
题意:有n道题目(编号1-n),每道题目有k个可能的选项,但是只有 h i h_i hi 是正确答案,每道题目一分。总共肯定有 k n k^n kn 中可能的答案序列,问有多少种答案序列a满足:将这个答案序列向右平移1之后(也就是原本答案a[i]现在拿去回答第 i%n+1 个问题),得到的分数比平移前高。
思路:如果 h [ i ] = h [ i % n + 1 ] h[i]=h[i\%n+1] h[i]=h[i%n+1],那么就不用考虑第 i 个问题了,因为无论怎样回答平移前后都是一样的结果。假设 h [ i ] ≠ h [ i % n + 1 ] h[i] \neq h[i\%n+1] h[i]=h[i%n+1] 的数目为 t,我们考虑计算平移前后分数不变的序列数,因为分数变多和变少是对称的,所以计算不变的数目 x,最终答案就是 ( k n − x ) / 2 (k^n-x)/2 (kn−x)/2。其它 n-t 个位置给 x 的贡献是累乘 k n − t k^{n-t} kn−t,t 个位置中,我们枚举 h[i]==a[i] 的数目q,很容易得出对于 q 的方案数为 C t q C t − q q ( k − 2 ) t − 2 q C_t^qC_{t-q}^q(k-2)^{t-2q} CtqCt−qq(k−2)t−2q,所以最终 x = k n − t ∑ q = 0 ⌊ t 2 ⌋ ( C t q C t − q q ( k − 2 ) t − 2 q ) x=k^{n-t} \sum_{q=0}^{\lfloor \frac{t}{2}\rfloor}(C_t^qC_{t-q}^q(k-2)^{t-2q}) x=kn−t∑q=0⌊2t⌋(CtqCt−qq(k−2)t−2q),答案为 ( k n − x ) / 2 (k^n-x)/2 (kn−x)/2。
另外题解是直接计算的,也可以,不过会麻烦一点点(要分奇偶讨论)
代码:
const int M=998244353;
const int maxn=2e5+5;
int n,k,jie[maxn],h[maxn],t;
int ksm(int x,int k)
{
int ret=1;
while(k)
{
if(k&1) ret=1ll*ret*x%M;
x=1ll*x*x%M;
k>>=1;
}
return ret;
}
int C(int n,int m)
{
if(!m || m>=n) return 1;
int ret=jie[n],temp=1ll*jie[m]*jie[n-m]%M;
ret=1ll*ret*ksm(temp,M-2)%M;
return ret;
}
int main()
{
//freopen("input.txt","r",stdin);
n=read(),k=read();
REP(i,1,n) h[i]=read();
REP(i,1,n) if(h[i]!=h[i%n+1]) t++;
jie[1]=1;
REP(i,2,n) jie[i]=1ll*jie[i-1]*i%M;
int ans=ksm(k,n-t),temp=0;
REP(q,0,t>>1)
{
int x=1ll*C(t,q)*C(t-q,q)%M*ksm(k-2,t-2*q)%M;
temp=(temp+x)%M;
}
ans=1ll*ans*temp%M;
temp=ksm(k,n);
cout<<1ll*(1ll*temp-ans+M)*ksm(2,M-2)%M;
return 0;
}