A - The Revenge of the Princess’ Knight(HDU 4898)
题意:给一个字符环(字符串的尾连着字符串的首),切成K个子串,使得字典序排列最大的串最小。
思路:%%%WJMZBMR,看到这个名字我就知道八成又一道我不会做的题。
正解是LCP+二分答案+奇葩贪心。
待补。
B - Civilization(CodeForces 455C)
题意:在N<=3*10^5个点初始M<N条边的图中两个操作:1、找给定点所在强连通分量上直径 2、通过建立一条边的方法,合并给定两个点所在的强连通分量。
思路:联通块的直径通过两次dfs计算。该题现场没做出来是因为没有证明出来一个问题:一个联通块里到所有点最长距离最短的点一定是直径的中点。可以通过反证法证明,如果存在不是直径中点的点到其它点距离更短,那么该直径不满足直径的定义。——于是,每次合并只需要把两个图的直径中点相连,再更新直径中点就好了。
代码:待补。
C - Serega and Fun(CodeForces 455D)
题意:N个数,每次操作为:1、L,R区间上整体轮换右移一位。2、询问L,R区间上等于K的数字有多少个。
思路:%%%陆队:分块链表+双端队列(蒟蒻没这个模板)
代码:待补。
D - Couple doubi(HDU 4861)
题意:有k个球,第i个球的价值为 ( 1^i+2^i+...+(p-1)^i ) MOD p。两人轮流每次拿一个球,问先手能不能赢。
思路:甜蜜虐狗向打表找规律题???上来我就列了个式子发现所有i为奇数的情况下,价值都是0,于是问题来了,偶数情况下还是不能算,是不是有规律呢?把100以内的表打出来一看笑了——标号为p-1的倍数的球才是非0的,值为p-1,于是结果出来了……
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
/*
int x[1010];
int val[1010];
void run(int temp)
{
val[1]=0;
for(int i=1;i<temp;i++)
{
x[i]=i;
val[1]+=x[i];
val[1]%=temp;
}
printf("1 %d\n",val[1]);
for(int i=2;i<=100;i++)
{
val[i]=0;
for(int j=1;j<temp;j++)
{
x[j]*=j;
x[j]%=temp;
val[i]+=x[j];
val[i]%=temp;
}
printf("%d %d\n",i,val[i]);
}
}
*/
int main()
{
/*
run(2);
run(3);
run(5);
run(7);
run(11);
*/
int k,p;
while(scanf("%d%d",&k,&p)!=EOF)
{
if(p==2)
{
if(k%2==0)
printf("NO\n");
else
printf("YES\n");
}
else
{
if((k/(p-1))%2==0)
printf("NO\n");
else
printf("YES\n");
}
}
return 0;
}
E - Paths on the tree(HDU 4912)
题意:一棵树,树上有M个给定的路径,问你这些路径不相交的情况下最多能选多少条。
思路:树形dp嘛,我上来就在想这个题,一直写到最后发现TLE了……首先要Tarjan处理出来所有路径的LCA,然后按照dp去写,dp[i][0]表示不选用i点,i为根的子树上最多选多少条,dp[i][1]表示选i点最多选多少条。那么dp[i][0]=∑dp[j][1](j为i的子节点) dp[i][1]=max(dp[i][0],dp[A][0]+(dp[a1][0]-dp[A][1])+(dp[a2][0]-dp[a1][1])+...+dp[B][0]+dp[b1][0]-dp[B][1]+...)其中Aa1a2...b1B为一条LCA是i点的给定路径。这个情况下其实是O(N^2)的复杂度,无法接受。怎么办呢?——把dp改成贪心,将路径按LCA的深度排序,你会发现优先选择前面的一定是最优解——同等深度的不会交叉,选择更高深度与更低深度时明显更低深度不会影响到选择其它边,所以选择更低的优。dp都不用写。由于你每次选择一个路径后就标记所有路径上的点,所以碰到被标记过的点就可以停,这样复杂度其实是O(N)的,可以接受。
//#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<deque>
using namespace std;
const int MAXN=100020,MAXM=100010;
int N,M,ufs[MAXN],dis[MAXN];
vector <int> p[MAXN];//存图
vector <pair<int,int> > quest[MAXN];//u节点有关的询问、询问编号
int LCA[MAXM];
struct tri
{
int a,b,lca;
}q[MAXM];
int f[MAXN],depth[MAXN];
bool cmp(tri x,tri y){return depth[x.lca]>depth[y.lca];}
int ans[MAXM];
bool vis[MAXN];
int find(int x){return ufs[x]==x?x:ufs[x]=find(ufs[x]);}
void Tarjan(int u)
{
vis[u]=true;
ufs[u]=u;
for(unsigned int i=0;i<quest[u].size();i++)
{
if(vis[quest[u][i].first])
LCA[quest[u][i].second]=find(quest[u][i].first);
}
for(unsigned int i=0;i<p[u].size();i++)
{
if(!vis[p[u][i]])
{
Tarjan(p[u][i]);
ufs[p[u][i]]=u;
}
}
}
void dfs(int x)
{
for(unsigned int i=0;i<p[x].size();i++)
{
if(p[x][i]==f[x])continue;
f[p[x][i]]=x;
depth[p[x][i]]=depth[x]+1;
dfs(p[x][i]);
}
}
int main()
{
while(scanf("%d%d",&N,&M)!=EOF)
{
memset(LCA,0,sizeof(LCA));
for(int i=1;i<=N;i++)ufs[i]=i;
int u,v;
for(int i=1;i<=N;i++)p[i].clear();
for(int i=1;i<N;i++)
{
scanf("%d%d",&u,&v);
p[u].push_back(v);
p[v].push_back(u);
}
for(int i=1;i<=N;i++)quest[i].clear();
for(int i=1;i<=M;i++)
{
scanf("%d%d",&u,&v);
quest[u].push_back(make_pair(v,i));
quest[v].push_back(make_pair(u,i));
q[i].a=u;
q[i].b=v;
}
memset(vis,false,sizeof(vis));
Tarjan(1);
for(int i=1;i<=M;i++)
q[i].lca=LCA[i];
depth[1]=1;
f[1]=0;
dfs(1);
sort(q+1,q+M+1,cmp);
memset(vis,false,sizeof(vis));
int lca;
bool flag;
int ans=0;
for(int i=1;i<=M;i++)
{
lca=q[i].lca;
flag=false;
if(vis[lca])flag=true;
if(!flag)
for(u=q[i].a;u!=lca;u=f[u])
if(vis[u])
{
flag=true;
break;
}
if(!flag)
for(v=q[i].b;v!=lca;v=f[v])
if(vis[v])
{
flag=true;
break;
}
if(!flag)
{
ans++;
vis[lca]=true;
for(u=q[i].a;u!=lca;u=f[u])
vis[u]=true;
for(v=q[i].b;v!=lca;v=f[v])
vis[v]=true;
}
}
printf("%d\n",ans);
}
return 0;
}
G - Boredom(
题意:10^5个数字,数值范围在10^5,每次取走一个数字x得x分,并且要把值为x-1和x+1的都删掉。问最大得分。
思路:选择了取x就可以把等于x的取完,而不能选择相邻的数字,可列dp方程:
ans[i]=max(ans[i-2]+(long long)i*num[i],ans[i-1]);
ans[i]代表取到数值i的时候最大得分。不是取i就是取i-1。是不相邻的取,满足题意。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int num[100010];
int N;
long long ans[100010];
int main()
{
scanf("%d",&N);
int temp;
memset(num,0,sizeof(num));
for(int i=1;i<=N;i++)
{
scanf("%d",&temp);
num[temp]++;
}
ans[0]=0;
ans[1]=num[1];
for(int i=2;i<=100000;i++)
{
ans[i]=max(ans[i-2]+(long long)i*num[i],ans[i-1]);
}
printf("%I64d\n",ans[100000]);
return 0;
}
题意:N<=10^5个字符串(总长度不超过10^5),拼一个word,两人轮流添加一个小写英文字符到word的末尾,如果word不能是这些字符串中任意一个字符串的前缀了就算失败。总共玩K<=10^9局,第i局的失败方为第i+1局的先手,如果两人都使用最优策略,问先手赢还是后手赢。
思路:建立Trie树,在Trie树上dp找到是否有先手必胜和先手必输策略——你想最后一局赢,需要一个先手必胜策略,因为如果后手必胜而且先手无法必胜,那么每轮的先手方必输,输了还是先手,最后一定是先手输。如果有先手必胜策略了,你还要有先手必输策略,这样你前K-1局都使用先手必输策略,再最后一局使用先手必胜策略,否则先手是必胜的,无法输,那只要判断K%2是不是1,如果是1则先手赢,是0则后手赢。
至于dp思路,0代表输,1代表赢。如果是叶子节点(某个非其它单词前缀的单词的末尾)标记为先手必胜,1(只有一个字符当然是先手必胜);非叶子节点,决策先手必输时,有0则为1,无0则为0,先手必胜时,有1则为0,有0则为1。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<string>
using namespace std;
int N,K,tot;
string str;
int len;
struct Node
{
//int f;
//char ch;
int son[26];
}tree[100010];
bool win[100010];
void insert(int node,int step)
{
if(step>=len)
{
return;
}
int tmp=str[step]-'a';
if(tree[node].son[tmp])
{
insert(tree[node].son[tmp],step+1);
}
else
{
tree[node].son[tmp]=++tot;
//tree[tot].ch=str[step];
//tree[tot].f=node;
for(int i=0;i<26;i++)tree[tot].son[i]=0;
insert(tree[node].son[tmp],step+1);
}
}
void dfs_lose(int x)
{
bool flag=true,zero=false;
for(int i=0;i<26;i++)
if(tree[x].son[i])
{
flag=false;
dfs_lose(tree[x].son[i]);
if(win[tree[x].son[i]]==0)
zero=true;
}
if(flag)
{
win[x]=true;
}
else
{
if(zero)
win[x]=true;
else
win[x]=false;
}
}
void dfs_win(int x)
{
bool flag=true,one=false;
for(int i=0;i<26;i++)
if(tree[x].son[i])
{
flag=false;
dfs_win(tree[x].son[i]);
if(win[tree[x].son[i]]==1)
one=true;
}
if(flag)
{
win[x]=true;
}
else
{
if(one)
win[x]=false;
else
win[x]=true;
}
}
int main()
{
cin.sync_with_stdio(false);
cin>>N>>K;
tot=0;
//tree[0].f=0;
for(int i=0;i<26;i++)tree[0].son[i]=0;
for(int i=1;i<=N;i++)
{
cin>>str;
len=str.length();
insert(0,0);
}
bool winner[2];
dfs_lose(0);
winner[0]=true;
for(int i=0;i<26;i++)
if(tree[0].son[i])
if(win[tree[0].son[i]]==0)
winner[0]=false;
dfs_win(0);
winner[1]=false;
for(int i=0;i<26;i++)
if(tree[0].son[i])
if(win[tree[0].son[i]])
winner[1]=true;
if(winner[0]==false && winner[1]==true)//有先手必输和先手必胜策略
{
printf("First\n");
}
else
{
if(winner[1]==true && K%2==1)//先手是必胜的 而且 总局数为奇数局
printf("First\n");
else
printf("Second\n");
}
return 0;
}