A题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5670
官方题解:
红、绿、蓝分别表示0、1、2,每次操作就相当于+1,原问题就转化为求n的三进制
表示的最低的m位,即求 n mod 3m的三进制表示。
复杂度 O(m)
我的理解:简单递推 不过读懂题意对我来说有点难
#include<cstdio> #include<cstring> using namespace std; #define ll __int64 char ch[5]="RGB"; char s[31]; int main() { int T,m,i; ll n; scanf("%d",&T); while(T--) { memset(s,0,sizeof(s)); scanf("%d%I64d",&m,&n); for(i=m-1;i>=0;i--) { s[i]=ch[n%3]; n/=3; } printf("%s\n",s); } }
官方题解:
对于交换行、交换列的操作,分别记录当前状态下每一行、每一列是原始数组的哪一行、哪一列即可。
对每一行、每一列加一个数的操作,也可以两个数组分别记录。注意当交换行、列的同时,也要交换增量数组。
输出时通过索引找到原矩阵中的值,再加上行、列的增量。
复杂度O(q+mn)
我的理解:简单题,用row col数组标记下当前矩阵的ij位置是原先的哪一行那一列即可,每次都在初始矩阵上进行加减操作
#include<cstdio> #include<cstring> using namespace std; const int N=1005; int num[1005][1005],row[N],line[N]; int i,j,n,m,q,tmp,T,a,b,c; int main() { scanf("%d",&T); while(T--) { scanf("%d%d%d",&n,&m,&q); for(i=1;i<=n;i++) { for(j=1;j<=m;j++) scanf("%d",&num[i][j]); } for(i=1;i<=n;i++) row[i]=i; for(i=1;i<=m;i++) line[i]=i; while(q--) { scanf("%d%d%d",&a,&b,&c); if(a==1) { int tmp=row[b]; row[b]=row[c]; row[c]=tmp; } else if(a==2) { int tmp=line[b]; line[b]=line[c]; line[c]=tmp; } else if(a==3) { for(i=1;i<=m;i++) num[row[b]][i]+=c; } else { for(i=1;i<=n;i++) num[i][line[b]]+=c; } } for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { if(j==1)printf("%d",num[row[i]][line[j]]); else printf(" %d",num[row[i]][line[j]]); } printf("\n"); } } return 0; }
官方题解:
有一个明显的性质:如果子串(i,j)包含了至少k个不同的字符,那么子串(i,k),(j<k<length)也包含了至少k个不同字符。
因此对于每一个左边界,只要找到最小的满足条件的右边界,就能在O(1)时间内统计完所有以这个左边界开始的符合条件的子串。
寻找这个右边界,是经典的追赶法(尺取法,双指针法)问题。维护两个指针(数组下标),轮流更新左右边界,同时累加答案即可。复杂度 O(length(S))。
我的理解:简单爆搜,从左往右搜,用cnt记录当前不同的字符个数,若cnt<k,while循环知道cnt=k为止,如果无法满足此条件说明不在存在该种字符串了,跳出循环输出答案
如果cnt==k,答案增加l-j,其中l为总长度,j为当前字符串尾的长度,i和j记录的是当前字符串的头和尾
#include<cstdio> #include<cstring> #define ll __int64 using namespace std; char s[1000005]; ll ans; int vis[26]; int main() { int T,l,k; scanf("%d",&T); while(T--) { memset(vis,0,sizeof(vis)); scanf("%s",s); l=strlen(s); scanf("%d",&k); ans=0; int i=0,j=0,cnt=1; vis[s[0]-'a']++; while(i<l) { while(cnt<k) { j++; if(j==l)break; if(vis[s[j]-'a']==0)cnt++; vis[s[j]-'a']++; } if(cnt<k)break; ans+=(l-j); vis[s[i]-'a']--; if(vis[s[i]-'a']==0)cnt--; i++; } printf("%I64d\n",ans); } }
官方题解:
记路径长度为n,那么机器人最多向右走⌊2n⌋步并向左走⌊2n⌋步。
Ans(n)=∑i=0⌊2n⌋Cn2i Catalan(i) 其中Catalan(n)表示第n个卡特兰数。
卡特兰数定义:Catalan(n)=n+1C2nn
递推公式Catalan(n)=n+14n−2 Catalan(n−1)
基于n的取值范围,此题可以预处理出1,000,001以内的乘法逆元、卡特兰数。
每次询问,都可以递推组合数,或者提前一次性预处理好阶乘和阶乘的逆元得到组合数;累加组合数与相应卡特兰数的乘积,得到答案。
事实上,Ans(n)是第n个默慈金数,还有更高效的递推公式:
Mn+1=Mn+∑i=0n−1MiMn−1−i=n+3(2n+3)Mn+3nMn−1。
我的思考:get新知识 1.卡特兰数 2.线性求逆元 3.默慈金数 此题为默慈金数的应用,也是卡特兰数的经典应用之一,基本上就是裸题,两者效率相差较大
#include<cstdio>//默慈金数 #define ll __int64 using namespace std; const int mod=1e9+7; const int N=1000009; ll dp[N],m[N]; int main() { int T,n; m[1]=1; for(int i=2;i<=N-9;i++) m[i]=((mod-mod/i)*m[mod%i])%mod; dp[1]=1;dp[2]=2;dp[3]=4; for(ll int i=4;i<=N-9;i++) { dp[i]=((dp[i-1]*(2*(i-1)+3)+3*(i-1)*dp[i-2])%mod*m[i-1+3])%mod; } scanf("%d",&T); while(T--) { scanf("%d",&n); printf("%d\n",dp[n]); } return 0; }
#include<cstdio>//卡特兰数 #include<cstring> using namespace std; #define ll __int64 const int N = 1000100; const int mod = 1000000000 + 7; ll inv[N],h[N],c[N]; int T,n,m; void init() { inv[1]=1; for(int i=2;i<N;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod; } int main() { init(); scanf("%d",&T); h[1]=h[0]=1; for(int i=2;i<N;i++) h[i]=h[i-1]*(4*i-2)%mod*inv[i+1]%mod; while(T--) { scanf("%d",&n); ll ans=1; c[0]=1; for(int i=1;i<=n;++i) c[i]=c[i-1]*(n-i+1)%mod*inv[i]%mod; for(int i=1;i<=n/2;++i) { int k=n-(i<<1); ans = (ans+h[i]*c[k])%mod; } printf("%I64d\n", ans); } return 0; }