这周并没有怎么刷题,大概切了几个水题,做个记录
题意:给定一个字符串,然后给出每个字母添加和删除的权值,问使其变成回文串的最小权值。
思路:简单的区间dp,直接dp权值即可。1.先考虑枚举的两个字母相同的情况,我们可以认为只要两个字母中间的字母已经回文这就是一个回文串了,得到dp公式,dp[i][j] = dp[i+1][j-1]。2.再考虑第二种情况,字母不相同的情况下,我们可以考虑,在前面添加或删去字符,或者在后面添加或删去字符。仔细思考,发现对于同一个字母而言,选择权值小的操作即可。得到dp公式,dp[i][j] = min(dp[i+1][j] + val[i],dp[i][j-1] + val[j])。
#include
#include
#include
#include
using namespace std;
const int N = 2e3+5;
int val[26],dp[N][N];
int main()
{
int x,y,n,m;
char k;
string s;
scanf("%d%d",&n,&m);
cin >> s;
memset(dp,0,sizeof(dp));
for(int i = 1;i <= n;++i){
scanf(" %c%d%d",&k,&x,&y);
val[k-'a'] = min(x,y);
}
for(int i = m-2;i >= 0;--i){
for(int j = i+1;j < m;++j){
if(s[i] == s[j]) dp[i][j] = dp[i+1][j-1];
else dp[i][j] = min(dp[i+1][j]+val[s[i]-'a'],dp[i][j-1]+val[s[j]-'a']);
}
}
printf("%d\n",dp[0][m-1]);
return 0;
}
题意:给定一个由两种括号组成的字符串,然后要求你将其补成合法,并且是多种答案中的最短串(任意一种即可)。
思路:刚开始想岔了,想着直接dp最短长度然后再操作,但这样在枚举中间点的时候,还要考虑头尾是否匹配,十分麻烦。看了一下大佬的思路,dp要增加的长度即可。此外要考虑空串空格的情况,所以回溯输出路径的时候要判符号再输出。1.若两个括弧匹配,那么可得dp公式,dp[i][j] = dp[i+1][j-1]。
2.然后考虑枚举中间点找最小值,dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j])。之后便是记录路径,对于最小值是原字符串内的括弧匹配,记录-1,为回溯终点,其他的记录k为划分点即可,简单回溯之后就可以得到答案了。
#include
#include
using namespace std;
const int N = 2e2+5;
const int INF = 0x3f3f3f3f;
int dp[N][N],path[N][N];
string s;
void init(int n){
for(int i = 0;i <= n;++i) dp[i][i] = 1;
return ;
}
inline bool check(char x,char y){
if(x == '(' && y == ')') return 1;
if(x == '[' && y == ']') return 1;
return 0;
}
void print(int l,int r){
if(l > r) return ;
if(l == r){
if(s[l] == '(' || s[l] == ')') printf("()");
if(s[l] == '[' || s[l] == ']') printf("[]");
return ;
}
else if(path[l][r] == -1){
printf("%c",s[l]);
print(l+1,r-1);
printf("%c",s[r]);
}
else{
print(l,path[l][r]);
print(path[l][r]+1,r);
}
return ;
}
int main()
{
while(getline(cin,s)){
int len = s.size()-1;
init(len);
for(int i = len-1;i >= 0;--i){
for(int j = i+1;j <= len;++j){
dp[i][j] = INF;
if(check(s[i],s[j])) dp[i][j] = dp[i+1][j-1],path[i][j] = -1;
for(int k = i;k <= j;++k){
if(dp[i][k]+dp[k+1][j] < dp[i][j]) dp[i][j] = dp[i][k]+dp[k+1][j],path[i][j] = k;
}
}
}
print(0,len);
printf("\n");
}
return 0;
}
题意:给定一个由两种括弧组成的串,问起子序列中最多有几个括弧是匹配的。
思路:简单的dp,1.若枚举的两个点字符相同,dp[i][j] = dp[i+1][j-1]+2。2.反之,枚举中间点,看哪种情况下匹配的括弧数最多即可,dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j])。
#include
#include
#include
#include
using namespace std;
const int N = 1e2+5;
int dp[N][N];
inline bool check(char x,char y){
if(x == '(' && y == ')') return 1;
if(x == '[' && y == ']') return 1;
return 0;
}
int main()
{
string s;
while(cin >> s && s != "end"){
int len = s.size();
memset(dp,0,sizeof(dp));
for(int i = len-2;i >= 0;--i){
for(int j = i+1;j < len;++j){
if(check(s[i],s[j])) dp[i][j] = dp[i+1][j-1]+1;
for(int k = i;k <= j;++k) dp[i][j] = max(dp[i][j],dp[i][k]+dp[k][j]);
}
}
printf("%d\n",dp[0][len-1] << 1);
}
return 0;
}
题意:经典的环形合并石子问题。
思路:环形合并石子的思路很简单,开两倍数组枚举长度dp即可,但这道题数据规模很大,需要用四边形不等式优化(这东西真没弄懂,晚上拉了一个模板把题目a了,还在研究阶段,有时间整理出来写一下)。
#include
using namespace std;
const int N = 2e3+5;
const int INF = 0x3f3f3f3f;
int dp[N][N],s[N],a[N],f[N][N];
void init(int n){
s[0] = 0;
for(int i = 1;i <= n;++i) dp[i][i] = 0,f[i][i] = i;
return ;
}
int main()
{
int n;
while(~scanf("%d",&n)){
init(n << 1);
for(int i = 1;i <= n;++i) scanf("%d",&a[i]),a[n+i] = a[i],s[i] = s[i-1]+a[i];
for(int i = n+1;i <= (n << 1);++i) s[i] = s[i-1]+a[i];
for(int r = 2;r <= n;++r){
for(int i = 1;i <= (n << 1)+1-r;++i){
int j = i+r-1,t;
dp[i][j] = INF;
for(int k = f[i][j-1];k <= f[i+1][j];++k){
if(dp[i][j] > dp[i][k]+dp[k+1][j]+s[j]-s[i-1]){
dp[i][j] = dp[i][k]+dp[k+1][j]+s[j]-s[i-1];
f[i][j] = k;
}
}
}
}
int ans = INF;
for(int i = 1;i <= n;++i) ans = min(ans,dp[i][i+n-1]);
printf("%d\n",ans);
}
return 0;
}
题意:给定一个字符串,求出其有多少个回文子序列。
思路:有一点点难,后面理了一下,发现还是可以dp的,和前面操作方式差不多。1.无论如何,一个大区间必然是小区间的值有所增加,那么考虑去头和去尾两个小区间内有多少个回文子序列,但因为会有重复的部分,所以我们要减去同时去头去尾的区间,得dp公式:dp[i][j] = (dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+M)%M,实际上这是一个比较简单的容斥原理的利用。2.考虑枚举的区间的头尾字符相同,那么该区间内回文子序列的数量还要加上去头去尾的区间内的回文子序列的数量+1,得dp公式:dp[i][j] = (dp[i][j]+dp[i+1][j-1]+1)%M;
#include
#include
using namespace std;
const int N = 1e3+5;
const int M = 10007;
int dp[N][N];
void init(int n){
for(int i = 0;i <= n;++i){
for(int j = 0;j <= n;++j) dp[i][j] = 0;
dp[i][i] = 1;
}
return ;
}
int main()
{
int T,cas = 0l;
string s;
scanf("%d",&T);
while(T--){
cin >> s;
int len = s.size()-1;
init(len);
for(int i = len-1;i >= 0;--i){
for(int j = i+1;j <= len;++j){
dp[i][j] = (dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+M)%M;
if(s[i] == s[j]) dp[i][j] = (dp[i][j]+dp[i+1][j-1]+1)%M;
}
}
printf("Case %d: %d\n",++cas,(dp[0][len]+M)%M);
}
return 0;
}
题意:两字兔子在一个环内反方向挑,挑的地方值要相同,仔细想一下可以发现就是求最长回文序列的长度。
思路:因为是环状,且数据规模不大,自动考虑双倍数组,然后考虑dp最长回文子序列的长度。1.若首尾字符相同,得dp公式dp[j][k] = dp[j+1][k-1]+2。2.若不相同,自然咋去头和去尾的两个区间内选取最大值dp[j][k] = max(dp[j+1][k],dp[j][k-1])。但要考虑两只兔子起点相同,可以看做是区间长度减一,并把第一步额外考虑即可。
#include
#include
using namespace std;
const int N = 2e3+5;
int dp[N][N],a[N];
void init(int n){
for(int i = 1;i <= n;++i){
for(int j = 1;j <= n;++j) dp[i][j] = 0;
dp[i][i] = 1;
}
return ;
}
void cins(int n){
for(int i = 1;i <= n;++i) scanf("%d",&a[i]),a[i+n] = a[i];
return ;
}
int main()
{
int n;
while(~scanf("%d",&n) && n){
init(n << 1);
cins(n);
for(int i = 2;i <= n;++i){
for(int j = 1;j <= (n << 1)+1-i;++j){
int k = j+i-1;
if(a[j] == a[k]) dp[j][k] = dp[j+1][k-1]+2;
else dp[j][k] = max(dp[j+1][k],dp[j][k-1]);
}
}
int ans = 0;
for(int i = 1;i <= n;++i) ans = max(max(dp[i][i+n-2]+1,dp[i][i+n-1]),ans);
printf("%d\n",ans);
}
return 0;
}
题意:这是一道炉石题(滑稽),大意是给了你每只恐狼的位置和邻位附加伤害以及本身的攻击力,要你求出打败所以恐狼承受的最小伤害是多少。
思路:先从
#include
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int p[64][55][55],d[55][55];
void ST(int n){
for(int l = 1;l < 64;++l){
for(int k = 1;k <= n;++k){
for(int i = 1;i <= n;++i){
for(int j = 1;j <= n;++j){
if(p[l-1][i][k] && p[l-1][k][j]){
p[l][i][j] = 1;
d[i][j] = 1;
}
}
}
}
}
return ;
}
void floyd(int n){
for(int k = 1;k <= n;++k){
for(int i = 1;i <= n;++i){
if(d[i][k] != INF){
for(int j = 1;j <= n;++j){
if(d[k][j] != INF){
d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}
}
return ;
}
int main()
{
int u,v,n,m;
scanf("%d%d",&n,&m);
memset(p,0,sizeof(p));
memset(d,INF,sizeof(d));
for(int i = 1;i <= m;++i){
scanf("%d%d",&u,&v);
d[u][v] = p[0][u][v] = 1;
}
ST(n);
floyd(n);
printf("%d\n",d[1][n]);
return 0;
}
题意:给一个同余方程,求解x的最小正解。
思路:虽然暑假里扩展欧几里得没学好,但这么裸的模板题还是完全没问题的。
#include
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
void exgcd(int a,int b,int &x,int &y){
if(!b){
x = 1,y = 0;
return ;
}
exgcd(b,a%b,y,x);
y -= (a/b)*x;
return ;
}
int main()
{
int x,y,a,b;
scanf("%d%d",&a,&b);
exgcd(a,b,x,y);
x = (x+b)%b;
printf("%d\n",x);
return 0;
}
题意:计算阶乘值最右边的非零位的值。
思路:这题我肯定在哪遇见过,印象太深刻了。。。对于已经为0的位置,在算乘法是无用的了,所以无脑吧0消掉就好了,但这题数据有点东西。。。取余的数字不对以及取余的位置不对就会tle(溢出到负数去了),然后明明感觉写过的我,交了5、6发才过。。。实在是太菜了。
#include
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll M = 1e7;
int main()
{
ll n;
scanf("%lld",&n);
ll ans = 1;
for(ll i = 2;i <= n;++i){
ans *= i;
while(!(ans%10)) ans /= 10;
ans %= M;
}
printf("%lld\n",ans%10);
return 0;
}
题意:1到N,问最小k原组能保证组内至少有一队数字不互质。
思路:很容易想到质数数量+1去,别问,问就是思维不行,实际推法可以发现,对于最大值n而言,直到n/2,都不会有数字互质,所以把这中间的值加上,然后再加1即可。
#include
using namespace std;
int main()
{
int n,T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
printf("%d\n",(n+1)/2+1);
}
return 0;
}
题意:问最小询问几次能找出公主所在的房间。
思路:发现前两个问题一点用都没有,然后直接把中立的人当反对的人看,直接考虑最坏清空,可以发现当b+c >= a,是不可能找到公主的,反之,询问(b+c)*2+1次就可以找到公主了,但是要考虑100的情况。。。因为只有一个人,那那个就只能是公主了。。。就不用询问了。
#include
using namespace std;
int main()
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(b == 0 && c == 0){
if(a == 1) printf("YES\n0\n");
else printf("YES\n1\n");
}
else if(a > b+c){
printf("YES\n%d\n",(b+c)*2+1);
}
else printf("NO\n");
return 0;
}
题意:给一个三角形,和三角形上的一个点,要求你找到另一个点,使连接的这条线能平分三角形的面积。
思路:这道数学题,真的不是我擅长的,但找到一个直接找点的方法https://zhidao.baidu.com/question/417422411.html,里面的第一个回答就是了,然后直接开始写代码,发现wa了无数发。。。然后和旁边要去ecfinal的队伍交流了一下,说是加了一个找到点之后再判一次是否再边上就过了,emm 我也加了一个,然后就过了。。。(wtf!!!,怀疑是精度问题???)
#include
using namespace std;
const double eps = 1e-8;
struct node{
double x,y;
node(){}
node(double x,double y):x(x),y(y){}
} p[4];
inline double cal_dis(node a,node b){
double s = b.y-a.y;
double t = b.x-a.x;
return sqrt(s*s+t*t);
}
inline bool judge_p_b(){
if(p[0].x == p[1].x && p[0].y == p[1].y) {printf("%.12lf %.12lf\n",(p[3].x+p[2].x)/2.0,(p[3].y+p[2].y)/2.0);return 1;}
if(p[0].x == p[2].x && p[0].y == p[2].y) {printf("%.12lf %.12lf\n",(p[3].x+p[1].x)/2.0,(p[3].y+p[1].y)/2.0);return 1;}
if(p[0].x == p[3].x && p[0].y == p[3].y) {printf("%.12lf %.12lf\n",(p[1].x+p[2].x)/2.0,(p[1].y+p[2].y)/2.0);return 1;}
if(p[0].x == (p[1].x+p[2].x)/2.0 && p[0].y == (p[1].y+p[2].y)/2.0) {printf("%.12lf %.12lf\n",p[3].x,p[3].y);return 1;}
if(p[0].x == (p[1].x+p[3].x)/2.0 && p[0].y == (p[1].y+p[3].y)/2.0) {printf("%.12lf %.12lf\n",p[2].x,p[2].y);return 1;}
if(p[0].x == (p[2].x+p[3].x)/2.0 && p[0].y == (p[2].y+p[3].y)/2.0) {printf("%.12lf %.12lf\n",p[1].x,p[1].y);return 1;}
return 0;
}
inline bool check(double c){
if(c <= eps && c >= -eps) return 1;
return 0;
}
void slove(double bl,node u,node v,node t){
node o((u.x+v.x)/2.0,(u.y+v.y)/2.0);
if(u.x == v.x){
double dis_y = o.y-u.y;
node ans(o.x,o.y+dis_y*bl);
if(check(cal_dis(ans,u)+cal_dis(ans,v)-cal_dis(u,v))) printf("%.12lf %.12lf\n",ans.x,ans.y);
else printf("-1\n");
return ;
}
else if(u.y == v.y){
double dis_x = o.x-u.x;
node ans(o.x+dis_x*bl,o.y);
if(check(cal_dis(ans,u)+cal_dis(ans,v)-cal_dis(u,v))) printf("%.12lf %.12lf\n",ans.x,ans.y);
else printf("-1\n");
return ;
}
else{
double dis_x = o.x-u.x;
double dis_y = o.y-u.y;
node ans(o.x+dis_x*bl,o.y+dis_y*bl);
if(check(cal_dis(ans,u)+cal_dis(ans,v)-cal_dis(u,v))) printf("%.12lf %.12lf\n",ans.x,ans.y);
else printf("-1\n");
return ;
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&p[1].x,&p[1].y,&p[2].x,&p[2].y,&p[3].x,&p[3].y,&p[0].x,&p[0].y);
double dis_12 = cal_dis(p[1],p[2]);
double dis_13 = cal_dis(p[1],p[3]);
double dis_23 = cal_dis(p[2],p[3]);
if(judge_p_b()) continue;
node u,v,t;
if(check(dis_12-cal_dis(p[0],p[1])-cal_dis(p[0],p[2]))) u = p[1],v = p[2],t = p[3];
else if(check(dis_13-cal_dis(p[0],p[1])-cal_dis(p[0],p[3]))) u = p[1],v = p[3],t = p[2];
else if(check(dis_23-cal_dis(p[0],p[2])-cal_dis(p[0],p[3]))) u = p[2],v = p[3],t = p[1];
else{printf("-1\n");continue;}
double dis_u = cal_dis(p[0],u);
double dis_v = cal_dis(p[0],v);
if(dis_u/dis_v > 1) slove(dis_v/dis_u,u,t,v);
else slove(dis_u/dis_v,v,t,u);
}
return 0;
}
题意:要求你找出图中有几个严格递增且长度大于等于4的路径
思路:之前暴力dfstle了,悄悄听了一会去ecfinal的大佬们的思路,分两种情况处理,对于每个点,如果是起始点(周围没有比他小一的点),从他开始dfs4步,给走到的地方权值加1,对于非起始点,把他的值推到所有相邻的值大一的点上去,并把自己的权值清零,没有的话不做操作。
但是还是wa了好几发。。。后来才发现,-1 0 1 2这种也是可行的路径。
中间自己想的一个思路想到一般的时候他们发现的。。。自己想的那个也是可行的,但还没写,矩阵转树,然后在树上做一个递推(非dp,没那么难),即当前长度为4的值为前一个结点长度为3的值,然后跑一遍拓扑排序即可,有时间会再写一个,赖队去南京写的和我这个差不多,但好一些,人家没先转成图,但思路是一样的。
#include
using namespace std;
typedef long long ll;
const int N = 1e3+5;
const ll M = 1e9+7;
struct node{
int x,y,val;
node(int x,int y,int val):x(x),y(y),val(val){}
};
vector <node> p;
int maps[N][N],n,m;
int mvx[] = {1,0,0,-1};
int mvy[] = {0,1,-1,0};
ll v[N][N];
bool cmp(node a,node b){
return a.val < b.val;
}
inline int read(){
register int s = 0,m = 1;
register char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') m = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {s = (s << 3)+(s << 1)+(ch^48);ch = getchar();}
return m*s;
}
void cins(){
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
maps[i][j] = read();
p.push_back(node(i,j,maps[i][j]));
}
}
sort(p.begin(),p.end(),cmp);
return ;
}
inline bool check(int x,int y,int k){
if(x < 1 || x > n || y < 1 || y > m || maps[x][y] != k) return 0;
return 1;
}
void dfs(int x,int y,int k){
if(maps[x][y] == k){
v[x][y]++;
v[x][y] %= M;
return ;
}
for(int i = 0;i < 4;++i){
int tx = x+mvx[i];
int ty = y+mvy[i];
if(check(tx,ty,maps[x][y]+1)) dfs(tx,ty,k);
}
return ;
}
void init(){
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
v[i][j] = 0ll;
}
}
return ;
}
inline bool judge(int x,int y){
bool f = 1,flag = 0;
for(int i = 0;i < 4;++i){
int tx = x+mvx[i];
int ty = y+mvy[i];
if(tx < 1 || tx > n || ty < 1 || ty > m) continue;
if(maps[tx][ty] == maps[x][y]+1) flag = 1,v[tx][ty] = (v[tx][ty]%M+v[x][y]%M)%M;
if(maps[tx][ty] == maps[x][y]-1) f = 0;
}
if(flag) v[x][y] = 0;
return f;
}
int main()
{
n = read();
m = read();
init();
cins();
ll ans = 0;
for(int i = 0;i < p.size();++i){
node t = p[i];
if(judge(t.x,t.y)) dfs(t.x,t.y,t.val+3);
}
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
ans = (ans+v[i][j]%M)%M;
}
}
printf("%lld\n",(ans+M)%M);
return 0;
}
题意:从序列中取三个值,然后按顺序排列,问不重复的值有多少个。
思路:n3复杂度必然超时,想了一下也就0~9总共10个数字,二进制hash一下,然后n2枚举没枚举过的前两个数字,然后增加贡献即可。(预处理从尾到头hash一遍即可)。
但是被zmh大佬暴力枚举000到999找位置的方法给完爆了。。。我就是个hape,思路不太行。
#include
using namespace std;
const int N = 3e4+5;
int a[N],k[N];
bool vis[10][10];
inline int cal(int x){
int res = 0;
while(x){
x -= (x&(-x));
res++;
}
return res;
}
int main()
{
int n;
scanf("%d",&n);
for(int i = 1;i <= n;++i) scanf("%1d",&a[i]);
int t = 0;
for(int i = n;i >= 1;--i){
t |= (1 << a[i]);
k[i] = t;
}
int ans = 0;
for(int i = 1;i <= n-2;++i){
for(int j = i+1;j <= n-1;++j){
if(vis[a[i]][a[j]]) continue;
vis[a[i]][a[j]] = 1;
ans += cal(k[j+1]);
}
}
printf("%d\n",ans);
return 0;
}
题意:每个人有一个Ai值,表示前面有几个人和自己帽子颜色相同,总共就三种颜色,刚开始想复杂想着dp,发现要四重循环,复杂度完全不允许,同队大佬推了一个递推累乘,不用管具体颜色,只看数量即可。。。沾大佬的光,过了第5题。
(感觉原来dp思路保留二维那种,再推一推就出来了。。。我还是太菜了。。。)
#include
using namespace std;
typedef long long ll;
const int N = 1e5+5;
const ll M = 1000000007;
int a[N],k[4];
int main()
{
int x,n;
scanf("%d",&n);
memset(k,0,sizeof(k));
ll ans = 1;
for(int i = 1;i <= n;++i) scanf("%d",&a[i]);
for(int i = 1;i <= n;++i){
int sum = 0;
for(int j = 1;j <= 3;++j){
if(k[j] == a[i]) sum++;
}
ans = (ans*sum)%M;
for(int j = 1;j <= 3;++j){
if(k[j] == a[i]){
k[j]++;
break;
}
}
}
printf("%lld\n",(ans+M)%M);
return 0;
}
咸鱼了好久好久的我,总算回到洛谷橙名了,可喜可乐可喜可乐。